diff --git a/contrib/pf/ftp-proxy/filter.c b/contrib/pf/ftp-proxy/filter.c index db3735565dac..dad6324808bc 100644 --- a/contrib/pf/ftp-proxy/filter.c +++ b/contrib/pf/ftp-proxy/filter.c @@ -1,399 +1,403 @@ /* $OpenBSD: filter.c,v 1.8 2008/06/13 07:25:26 claudio Exp $ */ /* * Copyright (c) 2004, 2005 Camiel Dobbelaar, * * 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 #include "filter.h" /* From netinet/in.h, but only _KERNEL_ gets them. */ #define satosin(sa) ((struct sockaddr_in *)(sa)) #define satosin6(sa) ((struct sockaddr_in6 *)(sa)) enum { TRANS_FILTER = 0, TRANS_NAT, TRANS_RDR, TRANS_SIZE }; int prepare_rule(u_int32_t, int, struct sockaddr *, struct sockaddr *, u_int16_t); int server_lookup4(struct sockaddr_in *, struct sockaddr_in *, struct sockaddr_in *); int server_lookup6(struct sockaddr_in6 *, struct sockaddr_in6 *, struct sockaddr_in6 *); static struct pfioc_pooladdr pfp; -static struct pfioc_rule pfr; +static struct pfctl_rule pfrule; +static char pfanchor[PF_ANCHOR_NAME_SIZE]; +static char pfanchor_call[PF_ANCHOR_NAME_SIZE]; +static uint32_t pfticket; +static uint32_t pfpool_ticket; static struct pfioc_trans pft; static struct pfioc_trans_e pfte[TRANS_SIZE]; static int dev, rule_log; static const char *qname, *tagname; int add_filter(u_int32_t id, u_int8_t dir, struct sockaddr *src, struct sockaddr *dst, u_int16_t d_port) { if (!src || !dst || !d_port) { errno = EINVAL; return (-1); } if (prepare_rule(id, PF_RULESET_FILTER, src, dst, d_port) == -1) return (-1); - pfr.rule.direction = dir; - if (pfctl_add_rule(dev, &pfr.rule, pfr.anchor, pfr.anchor_call, - pfr.ticket, pfr.pool_ticket)) + pfrule.direction = dir; + if (pfctl_add_rule(dev, &pfrule, pfanchor, pfanchor_call, + pfticket, pfpool_ticket)) return (-1); return (0); } int add_nat(u_int32_t id, struct sockaddr *src, struct sockaddr *dst, u_int16_t d_port, struct sockaddr *nat, u_int16_t nat_range_low, u_int16_t nat_range_high) { if (!src || !dst || !d_port || !nat || !nat_range_low || (src->sa_family != nat->sa_family)) { errno = EINVAL; return (-1); } if (prepare_rule(id, PF_RULESET_NAT, src, dst, d_port) == -1) return (-1); if (nat->sa_family == AF_INET) { memcpy(&pfp.addr.addr.v.a.addr.v4, &satosin(nat)->sin_addr.s_addr, 4); memset(&pfp.addr.addr.v.a.mask.addr8, 255, 4); } else { memcpy(&pfp.addr.addr.v.a.addr.v6, &satosin6(nat)->sin6_addr.s6_addr, 16); memset(&pfp.addr.addr.v.a.mask.addr8, 255, 16); } - if (pfctl_add_rule(dev, &pfr.rule, pfr.anchor, pfr.anchor_call, - pfr.ticket, pfr.pool_ticket)) + if (pfctl_add_rule(dev, &pfrule, pfanchor, pfanchor_call, + pfticket, pfpool_ticket)) return (-1); - pfr.rule.rpool.proxy_port[0] = nat_range_low; - pfr.rule.rpool.proxy_port[1] = nat_range_high; - if (pfctl_add_rule(dev, &pfr.rule, pfr.anchor, pfr.anchor_call, - pfr.ticket, pfr.pool_ticket)) + pfrule.rpool.proxy_port[0] = nat_range_low; + pfrule.rpool.proxy_port[1] = nat_range_high; + if (pfctl_add_rule(dev, &pfrule, pfanchor, pfanchor_call, + pfticket, pfpool_ticket)) return (-1); return (0); } int add_rdr(u_int32_t id, struct sockaddr *src, struct sockaddr *dst, u_int16_t d_port, struct sockaddr *rdr, u_int16_t rdr_port) { if (!src || !dst || !d_port || !rdr || !rdr_port || (src->sa_family != rdr->sa_family)) { errno = EINVAL; return (-1); } if (prepare_rule(id, PF_RULESET_RDR, src, dst, d_port) == -1) return (-1); if (rdr->sa_family == AF_INET) { memcpy(&pfp.addr.addr.v.a.addr.v4, &satosin(rdr)->sin_addr.s_addr, 4); memset(&pfp.addr.addr.v.a.mask.addr8, 255, 4); } else { memcpy(&pfp.addr.addr.v.a.addr.v6, &satosin6(rdr)->sin6_addr.s6_addr, 16); memset(&pfp.addr.addr.v.a.mask.addr8, 255, 16); } - if (pfctl_add_rule(dev, &pfr.rule, pfr.anchor, pfr.anchor_call, - pfr.ticket, pfr.pool_ticket)) + if (pfctl_add_rule(dev, &pfrule, pfanchor, pfanchor_call, + pfticket, pfpool_ticket)) return (-1); - pfr.rule.rpool.proxy_port[0] = rdr_port; - if (pfctl_add_rule(dev, &pfr.rule, pfr.anchor, pfr.anchor_call, - pfr.ticket, pfr.pool_ticket)) + pfrule.rpool.proxy_port[0] = rdr_port; + if (pfctl_add_rule(dev, &pfrule, pfanchor, pfanchor_call, + pfticket, pfpool_ticket)) return (-1); return (0); } int do_commit(void) { if (ioctl(dev, DIOCXCOMMIT, &pft) == -1) return (-1); return (0); } int do_rollback(void) { if (ioctl(dev, DIOCXROLLBACK, &pft) == -1) return (-1); return (0); } void init_filter(const char *opt_qname, const char *opt_tagname, int opt_verbose) { struct pf_status status; qname = opt_qname; tagname = opt_tagname; if (opt_verbose == 1) rule_log = PF_LOG; else if (opt_verbose == 2) rule_log = PF_LOG_ALL; dev = open("/dev/pf", O_RDWR); if (dev == -1) err(1, "open /dev/pf"); if (ioctl(dev, DIOCGETSTATUS, &status) == -1) err(1, "DIOCGETSTATUS"); if (!status.running) errx(1, "pf is disabled"); } int prepare_commit(u_int32_t id) { char an[PF_ANCHOR_NAME_SIZE]; int i; memset(&pft, 0, sizeof pft); pft.size = TRANS_SIZE; pft.esize = sizeof pfte[0]; pft.array = pfte; snprintf(an, PF_ANCHOR_NAME_SIZE, "%s/%d.%d", FTP_PROXY_ANCHOR, getpid(), id); for (i = 0; i < TRANS_SIZE; i++) { memset(&pfte[i], 0, sizeof pfte[0]); strlcpy(pfte[i].anchor, an, PF_ANCHOR_NAME_SIZE); switch (i) { case TRANS_FILTER: pfte[i].rs_num = PF_RULESET_FILTER; break; case TRANS_NAT: pfte[i].rs_num = PF_RULESET_NAT; break; case TRANS_RDR: pfte[i].rs_num = PF_RULESET_RDR; break; default: errno = EINVAL; return (-1); } } if (ioctl(dev, DIOCXBEGIN, &pft) == -1) return (-1); return (0); } int prepare_rule(u_int32_t id, int rs_num, struct sockaddr *src, struct sockaddr *dst, u_int16_t d_port) { char an[PF_ANCHOR_NAME_SIZE]; if ((src->sa_family != AF_INET && src->sa_family != AF_INET6) || (src->sa_family != dst->sa_family)) { errno = EPROTONOSUPPORT; return (-1); } memset(&pfp, 0, sizeof pfp); - memset(&pfr, 0, sizeof pfr); + memset(&pfrule, 0, sizeof pfrule); snprintf(an, PF_ANCHOR_NAME_SIZE, "%s/%d.%d", FTP_PROXY_ANCHOR, getpid(), id); strlcpy(pfp.anchor, an, PF_ANCHOR_NAME_SIZE); - strlcpy(pfr.anchor, an, PF_ANCHOR_NAME_SIZE); + strlcpy(pfanchor, an, PF_ANCHOR_NAME_SIZE); switch (rs_num) { case PF_RULESET_FILTER: - pfr.ticket = pfte[TRANS_FILTER].ticket; + pfticket = pfte[TRANS_FILTER].ticket; break; case PF_RULESET_NAT: - pfr.ticket = pfte[TRANS_NAT].ticket; + pfticket = pfte[TRANS_NAT].ticket; break; case PF_RULESET_RDR: - pfr.ticket = pfte[TRANS_RDR].ticket; + pfticket = pfte[TRANS_RDR].ticket; break; default: errno = EINVAL; return (-1); } if (ioctl(dev, DIOCBEGINADDRS, &pfp) == -1) return (-1); - pfr.pool_ticket = pfp.ticket; + pfpool_ticket = pfp.ticket; /* Generic for all rule types. */ - pfr.rule.af = src->sa_family; - pfr.rule.proto = IPPROTO_TCP; - pfr.rule.src.addr.type = PF_ADDR_ADDRMASK; - pfr.rule.dst.addr.type = PF_ADDR_ADDRMASK; + pfrule.af = src->sa_family; + pfrule.proto = IPPROTO_TCP; + pfrule.src.addr.type = PF_ADDR_ADDRMASK; + pfrule.dst.addr.type = PF_ADDR_ADDRMASK; if (src->sa_family == AF_INET) { - memcpy(&pfr.rule.src.addr.v.a.addr.v4, + memcpy(&pfrule.src.addr.v.a.addr.v4, &satosin(src)->sin_addr.s_addr, 4); - memset(&pfr.rule.src.addr.v.a.mask.addr8, 255, 4); - memcpy(&pfr.rule.dst.addr.v.a.addr.v4, + memset(&pfrule.src.addr.v.a.mask.addr8, 255, 4); + memcpy(&pfrule.dst.addr.v.a.addr.v4, &satosin(dst)->sin_addr.s_addr, 4); - memset(&pfr.rule.dst.addr.v.a.mask.addr8, 255, 4); + memset(&pfrule.dst.addr.v.a.mask.addr8, 255, 4); } else { - memcpy(&pfr.rule.src.addr.v.a.addr.v6, + memcpy(&pfrule.src.addr.v.a.addr.v6, &satosin6(src)->sin6_addr.s6_addr, 16); - memset(&pfr.rule.src.addr.v.a.mask.addr8, 255, 16); - memcpy(&pfr.rule.dst.addr.v.a.addr.v6, + memset(&pfrule.src.addr.v.a.mask.addr8, 255, 16); + memcpy(&pfrule.dst.addr.v.a.addr.v6, &satosin6(dst)->sin6_addr.s6_addr, 16); - memset(&pfr.rule.dst.addr.v.a.mask.addr8, 255, 16); + memset(&pfrule.dst.addr.v.a.mask.addr8, 255, 16); } - pfr.rule.dst.port_op = PF_OP_EQ; - pfr.rule.dst.port[0] = htons(d_port); + pfrule.dst.port_op = PF_OP_EQ; + pfrule.dst.port[0] = htons(d_port); switch (rs_num) { case PF_RULESET_FILTER: /* * pass [quick] [log] inet[6] proto tcp \ * from $src to $dst port = $d_port flags S/SA keep state * (max 1) [queue qname] [tag tagname] */ - pfr.rule.action = PF_PASS; - pfr.rule.quick = 1; - pfr.rule.log = rule_log; - pfr.rule.keep_state = 1; - pfr.rule.flags = TH_SYN; - pfr.rule.flagset = (TH_SYN|TH_ACK); - pfr.rule.max_states = 1; + pfrule.action = PF_PASS; + pfrule.quick = 1; + pfrule.log = rule_log; + pfrule.keep_state = 1; + pfrule.flags = TH_SYN; + pfrule.flagset = (TH_SYN|TH_ACK); + pfrule.max_states = 1; if (qname != NULL) - strlcpy(pfr.rule.qname, qname, sizeof pfr.rule.qname); + strlcpy(pfrule.qname, qname, sizeof pfrule.qname); if (tagname != NULL) { - pfr.rule.quick = 0; - strlcpy(pfr.rule.tagname, tagname, - sizeof pfr.rule.tagname); + pfrule.quick = 0; + strlcpy(pfrule.tagname, tagname, + sizeof pfrule.tagname); } break; case PF_RULESET_NAT: /* * nat inet[6] proto tcp from $src to $dst port $d_port -> $nat */ - pfr.rule.action = PF_NAT; + pfrule.action = PF_NAT; break; case PF_RULESET_RDR: /* * rdr inet[6] proto tcp from $src to $dst port $d_port -> $rdr */ - pfr.rule.action = PF_RDR; + pfrule.action = PF_RDR; break; default: errno = EINVAL; return (-1); } return (0); } int server_lookup(struct sockaddr *client, struct sockaddr *proxy, struct sockaddr *server) { if (client->sa_family == AF_INET) return (server_lookup4(satosin(client), satosin(proxy), satosin(server))); if (client->sa_family == AF_INET6) return (server_lookup6(satosin6(client), satosin6(proxy), satosin6(server))); errno = EPROTONOSUPPORT; return (-1); } int server_lookup4(struct sockaddr_in *client, struct sockaddr_in *proxy, struct sockaddr_in *server) { struct pfioc_natlook pnl; memset(&pnl, 0, sizeof pnl); pnl.direction = PF_OUT; pnl.af = AF_INET; pnl.proto = IPPROTO_TCP; memcpy(&pnl.saddr.v4, &client->sin_addr.s_addr, sizeof pnl.saddr.v4); memcpy(&pnl.daddr.v4, &proxy->sin_addr.s_addr, sizeof pnl.daddr.v4); pnl.sport = client->sin_port; pnl.dport = proxy->sin_port; if (ioctl(dev, DIOCNATLOOK, &pnl) == -1) return (-1); memset(server, 0, sizeof(struct sockaddr_in)); server->sin_len = sizeof(struct sockaddr_in); server->sin_family = AF_INET; memcpy(&server->sin_addr.s_addr, &pnl.rdaddr.v4, sizeof server->sin_addr.s_addr); server->sin_port = pnl.rdport; return (0); } int server_lookup6(struct sockaddr_in6 *client, struct sockaddr_in6 *proxy, struct sockaddr_in6 *server) { struct pfioc_natlook pnl; memset(&pnl, 0, sizeof pnl); pnl.direction = PF_OUT; pnl.af = AF_INET6; pnl.proto = IPPROTO_TCP; memcpy(&pnl.saddr.v6, &client->sin6_addr.s6_addr, sizeof pnl.saddr.v6); memcpy(&pnl.daddr.v6, &proxy->sin6_addr.s6_addr, sizeof pnl.daddr.v6); pnl.sport = client->sin6_port; pnl.dport = proxy->sin6_port; if (ioctl(dev, DIOCNATLOOK, &pnl) == -1) return (-1); memset(server, 0, sizeof(struct sockaddr_in6)); server->sin6_len = sizeof(struct sockaddr_in6); server->sin6_family = AF_INET6; memcpy(&server->sin6_addr.s6_addr, &pnl.rdaddr.v6, sizeof server->sin6_addr); server->sin6_port = pnl.rdport; return (0); } diff --git a/contrib/pf/tftp-proxy/filter.c b/contrib/pf/tftp-proxy/filter.c index 0b87d568809f..1689d3465fd3 100644 --- a/contrib/pf/tftp-proxy/filter.c +++ b/contrib/pf/tftp-proxy/filter.c @@ -1,408 +1,412 @@ /* $OpenBSD: filter.c,v 1.1 2005/12/28 19:07:07 jcs Exp $ */ /* $FreeBSD$ */ /* * Copyright (c) 2004, 2005 Camiel Dobbelaar, * * 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 #include #include #include "filter.h" /* From netinet/in.h, but only _KERNEL_ gets them. */ #define satosin(sa) ((struct sockaddr_in *)(sa)) #define satosin6(sa) ((struct sockaddr_in6 *)(sa)) enum { TRANS_FILTER = 0, TRANS_NAT, TRANS_RDR, TRANS_SIZE }; int prepare_rule(u_int32_t, int, struct sockaddr *, struct sockaddr *, u_int16_t, u_int8_t); int server_lookup4(struct sockaddr_in *, struct sockaddr_in *, struct sockaddr_in *, u_int8_t); int server_lookup6(struct sockaddr_in6 *, struct sockaddr_in6 *, struct sockaddr_in6 *, u_int8_t); static struct pfioc_pooladdr pfp; -static struct pfioc_rule pfr; +static struct pfctl_rule pfrule; +static uint32_t pfticket; +static uint32_t pfpool_ticket; +static char pfanchor[PF_ANCHOR_NAME_SIZE]; +static char pfanchor_call[PF_ANCHOR_NAME_SIZE]; static struct pfioc_trans pft; static struct pfioc_trans_e pfte[TRANS_SIZE]; static int dev, rule_log; static char *qname; int add_filter(u_int32_t id, u_int8_t dir, struct sockaddr *src, struct sockaddr *dst, u_int16_t d_port, u_int8_t proto) { if (!src || !dst || !d_port || !proto) { errno = EINVAL; return (-1); } if (prepare_rule(id, PF_RULESET_FILTER, src, dst, d_port, proto) == -1) return (-1); - pfr.rule.direction = dir; - if (pfctl_add_rule(dev, &pfr.rule, pfr.anchor, pfr.anchor_call, - pfr.ticket, pfr.pool_ticket)) + pfrule.direction = dir; + if (pfctl_add_rule(dev, &pfrule, pfanchor, pfanchor_call, + pfticket, pfpool_ticket)) return (-1); return (0); } int add_nat(u_int32_t id, struct sockaddr *src, struct sockaddr *dst, u_int16_t d_port, struct sockaddr *nat, u_int16_t nat_range_low, u_int16_t nat_range_high, u_int8_t proto) { if (!src || !dst || !d_port || !nat || !nat_range_low || !proto || (src->sa_family != nat->sa_family)) { errno = EINVAL; return (-1); } if (prepare_rule(id, PF_RULESET_NAT, src, dst, d_port, proto) == -1) return (-1); if (nat->sa_family == AF_INET) { memcpy(&pfp.addr.addr.v.a.addr.v4, &satosin(nat)->sin_addr.s_addr, 4); memset(&pfp.addr.addr.v.a.mask.addr8, 255, 4); } else { memcpy(&pfp.addr.addr.v.a.addr.v6, &satosin6(nat)->sin6_addr.s6_addr, 16); memset(&pfp.addr.addr.v.a.mask.addr8, 255, 16); } if (ioctl(dev, DIOCADDADDR, &pfp) == -1) return (-1); - pfr.rule.rpool.proxy_port[0] = nat_range_low; - pfr.rule.rpool.proxy_port[1] = nat_range_high; - if (pfctl_add_rule(dev, &pfr.rule, pfr.anchor, pfr.anchor_call, - pfr.ticket, pfr.pool_ticket)) + pfrule.rpool.proxy_port[0] = nat_range_low; + pfrule.rpool.proxy_port[1] = nat_range_high; + if (pfctl_add_rule(dev, &pfrule, pfanchor, pfanchor_call, + pfticket, pfpool_ticket)) return (-1); return (0); } int add_rdr(u_int32_t id, struct sockaddr *src, struct sockaddr *dst, u_int16_t d_port, struct sockaddr *rdr, u_int16_t rdr_port, u_int8_t proto) { if (!src || !dst || !d_port || !rdr || !rdr_port || !proto || (src->sa_family != rdr->sa_family)) { errno = EINVAL; return (-1); } if (prepare_rule(id, PF_RULESET_RDR, src, dst, d_port, proto) == -1) return (-1); if (rdr->sa_family == AF_INET) { memcpy(&pfp.addr.addr.v.a.addr.v4, &satosin(rdr)->sin_addr.s_addr, 4); memset(&pfp.addr.addr.v.a.mask.addr8, 255, 4); } else { memcpy(&pfp.addr.addr.v.a.addr.v6, &satosin6(rdr)->sin6_addr.s6_addr, 16); memset(&pfp.addr.addr.v.a.mask.addr8, 255, 16); } if (ioctl(dev, DIOCADDADDR, &pfp) == -1) return (-1); - pfr.rule.rpool.proxy_port[0] = rdr_port; - if (pfctl_add_rule(dev, &pfr.rule, pfr.anchor, pfr.anchor_call, - pfr.ticket, pfr.pool_ticket)) + pfrule.rpool.proxy_port[0] = rdr_port; + if (pfctl_add_rule(dev, &pfrule, pfanchor, pfanchor_call, + pfticket, pfpool_ticket)) return (-1); return (0); } int do_commit(void) { if (ioctl(dev, DIOCXCOMMIT, &pft) == -1) return (-1); return (0); } int do_rollback(void) { if (ioctl(dev, DIOCXROLLBACK, &pft) == -1) return (-1); return (0); } void init_filter(char *opt_qname, int opt_verbose) { struct pf_status status; qname = opt_qname; if (opt_verbose == 1) rule_log = PF_LOG; else if (opt_verbose == 2) rule_log = PF_LOG_ALL; dev = open("/dev/pf", O_RDWR); if (dev == -1) { syslog(LOG_ERR, "can't open /dev/pf"); exit(1); } if (ioctl(dev, DIOCGETSTATUS, &status) == -1) { syslog(LOG_ERR, "DIOCGETSTATUS"); exit(1); } if (!status.running) { syslog(LOG_ERR, "pf is disabled"); exit(1); } } int prepare_commit(u_int32_t id) { char an[PF_ANCHOR_NAME_SIZE]; int i; memset(&pft, 0, sizeof pft); pft.size = TRANS_SIZE; pft.esize = sizeof pfte[0]; pft.array = pfte; snprintf(an, PF_ANCHOR_NAME_SIZE, "%s/%d.%d", FTP_PROXY_ANCHOR, getpid(), id); for (i = 0; i < TRANS_SIZE; i++) { memset(&pfte[i], 0, sizeof pfte[0]); strlcpy(pfte[i].anchor, an, PF_ANCHOR_NAME_SIZE); switch (i) { case TRANS_FILTER: pfte[i].rs_num = PF_RULESET_FILTER; break; case TRANS_NAT: pfte[i].rs_num = PF_RULESET_NAT; break; case TRANS_RDR: pfte[i].rs_num = PF_RULESET_RDR; break; default: errno = EINVAL; return (-1); } } if (ioctl(dev, DIOCXBEGIN, &pft) == -1) return (-1); return (0); } int prepare_rule(u_int32_t id, int rs_num, struct sockaddr *src, struct sockaddr *dst, u_int16_t d_port, u_int8_t proto) { char an[PF_ANCHOR_NAME_SIZE]; if ((src->sa_family != AF_INET && src->sa_family != AF_INET6) || (src->sa_family != dst->sa_family)) { errno = EPROTONOSUPPORT; return (-1); } memset(&pfp, 0, sizeof pfp); - memset(&pfr, 0, sizeof pfr); + memset(&pfrule, 0, sizeof pfrule); snprintf(an, PF_ANCHOR_NAME_SIZE, "%s/%d.%d", FTP_PROXY_ANCHOR, getpid(), id); strlcpy(pfp.anchor, an, PF_ANCHOR_NAME_SIZE); - strlcpy(pfr.anchor, an, PF_ANCHOR_NAME_SIZE); + strlcpy(pfanchor, an, PF_ANCHOR_NAME_SIZE); switch (rs_num) { case PF_RULESET_FILTER: - pfr.ticket = pfte[TRANS_FILTER].ticket; + pfticket = pfte[TRANS_FILTER].ticket; break; case PF_RULESET_NAT: - pfr.ticket = pfte[TRANS_NAT].ticket; + pfticket = pfte[TRANS_NAT].ticket; break; case PF_RULESET_RDR: - pfr.ticket = pfte[TRANS_RDR].ticket; + pfticket = pfte[TRANS_RDR].ticket; break; default: errno = EINVAL; return (-1); } if (ioctl(dev, DIOCBEGINADDRS, &pfp) == -1) return (-1); - pfr.pool_ticket = pfp.ticket; + pfpool_ticket = pfp.ticket; /* Generic for all rule types. */ - pfr.rule.af = src->sa_family; - pfr.rule.proto = proto; - pfr.rule.src.addr.type = PF_ADDR_ADDRMASK; - pfr.rule.dst.addr.type = PF_ADDR_ADDRMASK; + pfrule.af = src->sa_family; + pfrule.proto = proto; + pfrule.src.addr.type = PF_ADDR_ADDRMASK; + pfrule.dst.addr.type = PF_ADDR_ADDRMASK; if (src->sa_family == AF_INET) { - memcpy(&pfr.rule.src.addr.v.a.addr.v4, + memcpy(&pfrule.src.addr.v.a.addr.v4, &satosin(src)->sin_addr.s_addr, 4); - memset(&pfr.rule.src.addr.v.a.mask.addr8, 255, 4); - memcpy(&pfr.rule.dst.addr.v.a.addr.v4, + memset(&pfrule.src.addr.v.a.mask.addr8, 255, 4); + memcpy(&pfrule.dst.addr.v.a.addr.v4, &satosin(dst)->sin_addr.s_addr, 4); - memset(&pfr.rule.dst.addr.v.a.mask.addr8, 255, 4); + memset(&pfrule.dst.addr.v.a.mask.addr8, 255, 4); } else { - memcpy(&pfr.rule.src.addr.v.a.addr.v6, + memcpy(&pfrule.src.addr.v.a.addr.v6, &satosin6(src)->sin6_addr.s6_addr, 16); - memset(&pfr.rule.src.addr.v.a.mask.addr8, 255, 16); - memcpy(&pfr.rule.dst.addr.v.a.addr.v6, + memset(&pfrule.src.addr.v.a.mask.addr8, 255, 16); + memcpy(&pfrule.dst.addr.v.a.addr.v6, &satosin6(dst)->sin6_addr.s6_addr, 16); - memset(&pfr.rule.dst.addr.v.a.mask.addr8, 255, 16); + memset(&pfrule.dst.addr.v.a.mask.addr8, 255, 16); } - pfr.rule.dst.port_op = PF_OP_EQ; - pfr.rule.dst.port[0] = htons(d_port); + pfrule.dst.port_op = PF_OP_EQ; + pfrule.dst.port[0] = htons(d_port); switch (rs_num) { case PF_RULESET_FILTER: /* * pass quick [log] inet[6] proto tcp \ * from $src to $dst port = $d_port flags S/SAFR keep state * (max 1) [queue qname] */ - pfr.rule.action = PF_PASS; - pfr.rule.quick = 1; - pfr.rule.log = rule_log; - pfr.rule.keep_state = 1; + pfrule.action = PF_PASS; + pfrule.quick = 1; + pfrule.log = rule_log; + pfrule.keep_state = 1; #ifdef __FreeBSD__ - pfr.rule.flags = (proto == IPPROTO_TCP ? TH_SYN : 0); - pfr.rule.flagset = (proto == IPPROTO_TCP ? + pfrule.flags = (proto == IPPROTO_TCP ? TH_SYN : 0); + pfrule.flagset = (proto == IPPROTO_TCP ? (TH_SYN|TH_ACK|TH_FIN|TH_RST) : 0); #else - pfr.rule.flags = (proto == IPPROTO_TCP ? TH_SYN : NULL); - pfr.rule.flagset = (proto == IPPROTO_TCP ? + pfrule.flags = (proto == IPPROTO_TCP ? TH_SYN : NULL); + pfrule.flagset = (proto == IPPROTO_TCP ? (TH_SYN|TH_ACK|TH_FIN|TH_RST) : NULL); #endif - pfr.rule.max_states = 1; + pfrule.max_states = 1; if (qname != NULL) - strlcpy(pfr.rule.qname, qname, sizeof pfr.rule.qname); + strlcpy(pfrule.qname, qname, sizeof pfrule.qname); break; case PF_RULESET_NAT: /* * nat inet[6] proto tcp from $src to $dst port $d_port -> $nat */ - pfr.rule.action = PF_NAT; + pfrule.action = PF_NAT; break; case PF_RULESET_RDR: /* * rdr inet[6] proto tcp from $src to $dst port $d_port -> $rdr */ - pfr.rule.action = PF_RDR; + pfrule.action = PF_RDR; break; default: errno = EINVAL; return (-1); } return (0); } int server_lookup(struct sockaddr *client, struct sockaddr *proxy, struct sockaddr *server, u_int8_t proto) { if (client->sa_family == AF_INET) return (server_lookup4(satosin(client), satosin(proxy), satosin(server), proto)); if (client->sa_family == AF_INET6) return (server_lookup6(satosin6(client), satosin6(proxy), satosin6(server), proto)); errno = EPROTONOSUPPORT; return (-1); } int server_lookup4(struct sockaddr_in *client, struct sockaddr_in *proxy, struct sockaddr_in *server, u_int8_t proto) { struct pfioc_natlook pnl; memset(&pnl, 0, sizeof pnl); pnl.direction = PF_OUT; pnl.af = AF_INET; pnl.proto = proto; memcpy(&pnl.saddr.v4, &client->sin_addr.s_addr, sizeof pnl.saddr.v4); memcpy(&pnl.daddr.v4, &proxy->sin_addr.s_addr, sizeof pnl.daddr.v4); pnl.sport = client->sin_port; pnl.dport = proxy->sin_port; if (ioctl(dev, DIOCNATLOOK, &pnl) == -1) return (-1); memset(server, 0, sizeof(struct sockaddr_in)); server->sin_len = sizeof(struct sockaddr_in); server->sin_family = AF_INET; memcpy(&server->sin_addr.s_addr, &pnl.rdaddr.v4, sizeof server->sin_addr.s_addr); server->sin_port = pnl.rdport; return (0); } int server_lookup6(struct sockaddr_in6 *client, struct sockaddr_in6 *proxy, struct sockaddr_in6 *server, u_int8_t proto) { struct pfioc_natlook pnl; memset(&pnl, 0, sizeof pnl); pnl.direction = PF_OUT; pnl.af = AF_INET6; pnl.proto = proto; memcpy(&pnl.saddr.v6, &client->sin6_addr.s6_addr, sizeof pnl.saddr.v6); memcpy(&pnl.daddr.v6, &proxy->sin6_addr.s6_addr, sizeof pnl.daddr.v6); pnl.sport = client->sin6_port; pnl.dport = proxy->sin6_port; if (ioctl(dev, DIOCNATLOOK, &pnl) == -1) return (-1); memset(server, 0, sizeof(struct sockaddr_in6)); server->sin6_len = sizeof(struct sockaddr_in6); server->sin6_family = AF_INET6; memcpy(&server->sin6_addr.s6_addr, &pnl.rdaddr.v6, sizeof server->sin6_addr); server->sin6_port = pnl.rdport; return (0); } diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c index e0d429112f5b..dc174d8d5b72 100644 --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -1,536 +1,536 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Rubicon Communications, LLC (Netgate) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libpfctl.h" static void pf_nvuint_8_array(const nvlist_t *nvl, const char *name, size_t maxelems, u_int8_t *numbers, size_t *nelems) { const uint64_t *tmp; size_t elems; tmp = nvlist_get_number_array(nvl, name, &elems); assert(elems <= maxelems); for (size_t i = 0; i < elems; i++) numbers[i] = tmp[i]; if (nelems) *nelems = elems; } static void pf_nvuint_16_array(const nvlist_t *nvl, const char *name, size_t maxelems, u_int16_t *numbers, size_t *nelems) { const uint64_t *tmp; size_t elems; tmp = nvlist_get_number_array(nvl, name, &elems); assert(elems <= maxelems); for (size_t i = 0; i < elems; i++) numbers[i] = tmp[i]; if (nelems) *nelems = elems; } static void pf_nvuint_32_array(const nvlist_t *nvl, const char *name, size_t maxelems, u_int32_t *numbers, size_t *nelems) { const uint64_t *tmp; size_t elems; tmp = nvlist_get_number_array(nvl, name, &elems); assert(elems <= maxelems); for (size_t i = 0; i < elems; i++) numbers[i] = tmp[i]; if (nelems) *nelems = elems; } static void pf_nvuint_64_array(const nvlist_t *nvl, const char *name, size_t maxelems, u_int64_t *numbers, size_t *nelems) { const uint64_t *tmp; size_t elems; tmp = nvlist_get_number_array(nvl, name, &elems); assert(elems <= maxelems); for (size_t i = 0; i < elems; i++) numbers[i] = tmp[i]; if (nelems) *nelems = elems; } static void pfctl_nv_add_addr(nvlist_t *nvparent, const char *name, const struct pf_addr *addr) { nvlist_t *nvl = nvlist_create(0); nvlist_add_binary(nvl, "addr", addr, sizeof(*addr)); nvlist_add_nvlist(nvparent, name, nvl); } static void pf_nvaddr_to_addr(const nvlist_t *nvl, struct pf_addr *addr) { size_t len; const void *data; data = nvlist_get_binary(nvl, "addr", &len); assert(len == sizeof(struct pf_addr)); memcpy(addr, data, len); } static void pfctl_nv_add_addr_wrap(nvlist_t *nvparent, const char *name, const struct pf_addr_wrap *addr) { nvlist_t *nvl = nvlist_create(0); nvlist_add_number(nvl, "type", addr->type); nvlist_add_number(nvl, "iflags", addr->iflags); nvlist_add_string(nvl, "ifname", addr->v.ifname); nvlist_add_string(nvl, "tblname", addr->v.tblname); pfctl_nv_add_addr(nvl, "addr", &addr->v.a.addr); pfctl_nv_add_addr(nvl, "mask", &addr->v.a.mask); nvlist_add_nvlist(nvparent, name, nvl); } static void pf_nvaddr_wrap_to_addr_wrap(const nvlist_t *nvl, struct pf_addr_wrap *addr) { addr->type = nvlist_get_number(nvl, "type"); addr->iflags = nvlist_get_number(nvl, "iflags"); strlcpy(addr->v.ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); strlcpy(addr->v.tblname, nvlist_get_string(nvl, "tblname"), PF_TABLE_NAME_SIZE); pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "addr"), &addr->v.a.addr); pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "mask"), &addr->v.a.mask); } static void pfctl_nv_add_rule_addr(nvlist_t *nvparent, const char *name, const struct pf_rule_addr *addr) { u_int64_t ports[2]; nvlist_t *nvl = nvlist_create(0); pfctl_nv_add_addr_wrap(nvl, "addr", &addr->addr); ports[0] = addr->port[0]; ports[1] = addr->port[1]; nvlist_add_number_array(nvl, "port", ports, 2); nvlist_add_number(nvl, "neg", addr->neg); nvlist_add_number(nvl, "port_op", addr->port_op); nvlist_add_nvlist(nvparent, name, nvl); } static void pf_nvrule_addr_to_rule_addr(const nvlist_t *nvl, struct pf_rule_addr *addr) { pf_nvaddr_wrap_to_addr_wrap(nvlist_get_nvlist(nvl, "addr"), &addr->addr); pf_nvuint_16_array(nvl, "port", 2, addr->port, NULL); addr->neg = nvlist_get_number(nvl, "neg"); addr->port_op = nvlist_get_number(nvl, "port_op"); } static void pfctl_nv_add_pool(nvlist_t *nvparent, const char *name, const struct pf_pool *pool) { u_int64_t ports[2]; nvlist_t *nvl = nvlist_create(0); nvlist_add_binary(nvl, "key", &pool->key, sizeof(pool->key)); pfctl_nv_add_addr(nvl, "counter", &pool->counter); nvlist_add_number(nvl, "tblidx", pool->tblidx); ports[0] = pool->proxy_port[0]; ports[1] = pool->proxy_port[1]; nvlist_add_number_array(nvl, "proxy_port", ports, 2); nvlist_add_number(nvl, "opts", pool->opts); nvlist_add_nvlist(nvparent, name, nvl); } static void pf_nvpool_to_pool(const nvlist_t *nvl, struct pf_pool *pool) { size_t len; const void *data; data = nvlist_get_binary(nvl, "key", &len); assert(len == sizeof(pool->key)); memcpy(&pool->key, data, len); pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "counter"), &pool->counter); pool->tblidx = nvlist_get_number(nvl, "tblidx"); pf_nvuint_16_array(nvl, "proxy_port", 2, pool->proxy_port, NULL); pool->opts = nvlist_get_number(nvl, "opts"); } static void pfctl_nv_add_uid(nvlist_t *nvparent, const char *name, const struct pf_rule_uid *uid) { u_int64_t uids[2]; nvlist_t *nvl = nvlist_create(0); uids[0] = uid->uid[0]; uids[1] = uid->uid[1]; nvlist_add_number_array(nvl, "uid", uids, 2); nvlist_add_number(nvl, "op", uid->op); nvlist_add_nvlist(nvparent, name, nvl); } static void pf_nvrule_uid_to_rule_uid(const nvlist_t *nvl, struct pf_rule_uid *uid) { pf_nvuint_32_array(nvl, "uid", 2, uid->uid, NULL); uid->op = nvlist_get_number(nvl, "op"); } static void pfctl_nv_add_divert(nvlist_t *nvparent, const char *name, - const struct pf_rule *r) + const struct pfctl_rule *r) { nvlist_t *nvl = nvlist_create(0); pfctl_nv_add_addr(nvl, "addr", &r->divert.addr); nvlist_add_number(nvl, "port", r->divert.port); nvlist_add_nvlist(nvparent, name, nvl); } static void -pf_nvdivert_to_divert(const nvlist_t *nvl, struct pf_rule *rule) +pf_nvdivert_to_divert(const nvlist_t *nvl, struct pfctl_rule *rule) { pf_nvaddr_to_addr(nvlist_get_nvlist(nvl, "addr"), &rule->divert.addr); rule->divert.port = nvlist_get_number(nvl, "port"); } static void -pf_nvrule_to_rule(const nvlist_t *nvl, struct pf_rule *rule) +pf_nvrule_to_rule(const nvlist_t *nvl, struct pfctl_rule *rule) { const uint64_t *skip; size_t skipcount; rule->nr = nvlist_get_number(nvl, "nr"); pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "src"), &rule->src); pf_nvrule_addr_to_rule_addr(nvlist_get_nvlist(nvl, "dst"), &rule->dst); skip = nvlist_get_number_array(nvl, "skip", &skipcount); assert(skip); assert(skipcount == PF_SKIP_COUNT); for (int i = 0; i < PF_SKIP_COUNT; i++) rule->skip[i].nr = skip[i]; strlcpy(rule->label, nvlist_get_string(nvl, "label"), PF_RULE_LABEL_SIZE); strlcpy(rule->ifname, nvlist_get_string(nvl, "ifname"), IFNAMSIZ); strlcpy(rule->qname, nvlist_get_string(nvl, "qname"), PF_QNAME_SIZE); strlcpy(rule->pqname, nvlist_get_string(nvl, "pqname"), PF_QNAME_SIZE); strlcpy(rule->tagname, nvlist_get_string(nvl, "tagname"), PF_TAG_NAME_SIZE); strlcpy(rule->match_tagname, nvlist_get_string(nvl, "match_tagname"), PF_TAG_NAME_SIZE); strlcpy(rule->overload_tblname, nvlist_get_string(nvl, "overload_tblname"), PF_TABLE_NAME_SIZE); pf_nvpool_to_pool(nvlist_get_nvlist(nvl, "rpool"), &rule->rpool); rule->evaluations = nvlist_get_number(nvl, "evaluations"); pf_nvuint_64_array(nvl, "packets", 2, rule->packets, NULL); pf_nvuint_64_array(nvl, "bytes", 2, rule->bytes, NULL); rule->os_fingerprint = nvlist_get_number(nvl, "os_fingerprint"); rule->rtableid = nvlist_get_number(nvl, "rtableid"); pf_nvuint_32_array(nvl, "timeout", PFTM_MAX, rule->timeout, NULL); rule->max_states = nvlist_get_number(nvl, "max_states"); rule->max_src_nodes = nvlist_get_number(nvl, "max_src_nodes"); rule->max_src_states = nvlist_get_number(nvl, "max_src_states"); rule->max_src_conn = nvlist_get_number(nvl, "max_src_conn"); rule->max_src_conn_rate.limit = nvlist_get_number(nvl, "max_src_conn_rate.limit"); rule->max_src_conn_rate.seconds = nvlist_get_number(nvl, "max_src_conn_rate.seconds"); rule->qid = nvlist_get_number(nvl, "qid"); rule->pqid = nvlist_get_number(nvl, "pqid"); rule->prob = nvlist_get_number(nvl, "prob"); rule->cuid = nvlist_get_number(nvl, "cuid"); rule->cpid = nvlist_get_number(nvl, "cpid"); rule->return_icmp = nvlist_get_number(nvl, "return_icmp"); rule->return_icmp6 = nvlist_get_number(nvl, "return_icmp6"); rule->max_mss = nvlist_get_number(nvl, "max_mss"); rule->scrub_flags = nvlist_get_number(nvl, "scrub_flags"); pf_nvrule_uid_to_rule_uid(nvlist_get_nvlist(nvl, "uid"), &rule->uid); pf_nvrule_uid_to_rule_uid(nvlist_get_nvlist(nvl, "gid"), (struct pf_rule_uid *)&rule->gid); rule->rule_flag = nvlist_get_number(nvl, "rule_flag"); rule->action = nvlist_get_number(nvl, "action"); rule->direction = nvlist_get_number(nvl, "direction"); rule->log = nvlist_get_number(nvl, "log"); rule->logif = nvlist_get_number(nvl, "logif"); rule->quick = nvlist_get_number(nvl, "quick"); rule->ifnot = nvlist_get_number(nvl, "ifnot"); rule->match_tag_not = nvlist_get_number(nvl, "match_tag_not"); rule->natpass = nvlist_get_number(nvl, "natpass"); rule->keep_state = nvlist_get_number(nvl, "keep_state"); rule->af = nvlist_get_number(nvl, "af"); rule->proto = nvlist_get_number(nvl, "proto"); rule->type = nvlist_get_number(nvl, "type"); rule->code = nvlist_get_number(nvl, "code"); rule->flags = nvlist_get_number(nvl, "flags"); rule->flagset = nvlist_get_number(nvl, "flagset"); rule->min_ttl = nvlist_get_number(nvl, "min_ttl"); rule->allow_opts = nvlist_get_number(nvl, "allow_opts"); rule->rt = nvlist_get_number(nvl, "rt"); rule->return_ttl = nvlist_get_number(nvl, "return_ttl"); rule->tos = nvlist_get_number(nvl, "tos"); rule->set_tos = nvlist_get_number(nvl, "set_tos"); rule->anchor_relative = nvlist_get_number(nvl, "anchor_relative"); rule->anchor_wildcard = nvlist_get_number(nvl, "anchor_wildcard"); rule->flush = nvlist_get_number(nvl, "flush"); rule->prio = nvlist_get_number(nvl, "prio"); pf_nvuint_8_array(nvl, "set_prio", 2, rule->set_prio, NULL); pf_nvdivert_to_divert(nvlist_get_nvlist(nvl, "divert"), rule); rule->u_states_cur = nvlist_get_number(nvl, "states_cur"); rule->u_states_tot = nvlist_get_number(nvl, "states_tot"); rule->u_src_nodes = nvlist_get_number(nvl, "src_nodes"); } int -pfctl_add_rule(int dev, const struct pf_rule *r, const char *anchor, +pfctl_add_rule(int dev, const struct pfctl_rule *r, const char *anchor, const char *anchor_call, u_int32_t ticket, u_int32_t pool_ticket) { struct pfioc_nv nv; u_int64_t timeouts[PFTM_MAX]; u_int64_t set_prio[2]; nvlist_t *nvl, *nvlr; int ret; nvl = nvlist_create(0); nvlr = nvlist_create(0); nvlist_add_number(nvl, "ticket", ticket); nvlist_add_number(nvl, "pool_ticket", pool_ticket); nvlist_add_string(nvl, "anchor", anchor); nvlist_add_string(nvl, "anchor_call", anchor_call); nvlist_add_number(nvlr, "nr", r->nr); pfctl_nv_add_rule_addr(nvlr, "src", &r->src); pfctl_nv_add_rule_addr(nvlr, "dst", &r->dst); nvlist_add_string(nvlr, "label", r->label); nvlist_add_string(nvlr, "ifname", r->ifname); nvlist_add_string(nvlr, "qname", r->qname); nvlist_add_string(nvlr, "pqname", r->pqname); nvlist_add_string(nvlr, "tagname", r->tagname); nvlist_add_string(nvlr, "match_tagname", r->match_tagname); nvlist_add_string(nvlr, "overload_tblname", r->overload_tblname); pfctl_nv_add_pool(nvlr, "rpool", &r->rpool); nvlist_add_number(nvlr, "os_fingerprint", r->os_fingerprint); nvlist_add_number(nvlr, "rtableid", r->rtableid); for (int i = 0; i < PFTM_MAX; i++) timeouts[i] = r->timeout[i]; nvlist_add_number_array(nvlr, "timeout", timeouts, PFTM_MAX); nvlist_add_number(nvlr, "max_states", r->max_states); nvlist_add_number(nvlr, "max_src_nodes", r->max_src_nodes); nvlist_add_number(nvlr, "max_src_states", r->max_src_states); nvlist_add_number(nvlr, "max_src_conn", r->max_src_conn); nvlist_add_number(nvlr, "max_src_conn_rate.limit", r->max_src_conn_rate.limit); nvlist_add_number(nvlr, "max_src_conn_rate.seconds", r->max_src_conn_rate.seconds); nvlist_add_number(nvlr, "prob", r->prob); nvlist_add_number(nvlr, "cuid", r->cuid); nvlist_add_number(nvlr, "cpid", r->cpid); nvlist_add_number(nvlr, "return_icmp", r->return_icmp); nvlist_add_number(nvlr, "return_icmp6", r->return_icmp6); nvlist_add_number(nvlr, "max_mss", r->max_mss); nvlist_add_number(nvlr, "scrub_flags", r->scrub_flags); pfctl_nv_add_uid(nvlr, "uid", &r->uid); pfctl_nv_add_uid(nvlr, "gid", (const struct pf_rule_uid *)&r->gid); nvlist_add_number(nvlr, "rule_flag", r->rule_flag); nvlist_add_number(nvlr, "action", r->action); nvlist_add_number(nvlr, "direction", r->direction); nvlist_add_number(nvlr, "log", r->log); nvlist_add_number(nvlr, "logif", r->logif); nvlist_add_number(nvlr, "quick", r->quick); nvlist_add_number(nvlr, "ifnot", r->ifnot); nvlist_add_number(nvlr, "match_tag_not", r->match_tag_not); nvlist_add_number(nvlr, "natpass", r->natpass); nvlist_add_number(nvlr, "keep_state", r->keep_state); nvlist_add_number(nvlr, "af", r->af); nvlist_add_number(nvlr, "proto", r->proto); nvlist_add_number(nvlr, "type", r->type); nvlist_add_number(nvlr, "code", r->code); nvlist_add_number(nvlr, "flags", r->flags); nvlist_add_number(nvlr, "flagset", r->flagset); nvlist_add_number(nvlr, "min_ttl", r->min_ttl); nvlist_add_number(nvlr, "allow_opts", r->allow_opts); nvlist_add_number(nvlr, "rt", r->rt); nvlist_add_number(nvlr, "return_ttl", r->return_ttl); nvlist_add_number(nvlr, "tos", r->tos); nvlist_add_number(nvlr, "set_tos", r->set_tos); nvlist_add_number(nvlr, "anchor_relative", r->anchor_relative); nvlist_add_number(nvlr, "anchor_wildcard", r->anchor_wildcard); nvlist_add_number(nvlr, "flush", r->flush); nvlist_add_number(nvlr, "prio", r->prio); set_prio[0] = r->set_prio[0]; set_prio[1] = r->set_prio[1]; nvlist_add_number_array(nvlr, "set_prio", set_prio, 2); pfctl_nv_add_divert(nvlr, "divert", r); nvlist_add_nvlist(nvl, "rule", nvlr); /* Now do the call. */ nv.data = nvlist_pack(nvl, &nv.len); nv.size = nv.len; ret = ioctl(dev, DIOCADDRULENV, &nv); free(nv.data); nvlist_destroy(nvl); return (ret); } int pfctl_get_rule(int dev, u_int32_t nr, u_int32_t ticket, const char *anchor, - u_int32_t ruleset, struct pf_rule *rule, char *anchor_call) + u_int32_t ruleset, struct pfctl_rule *rule, char *anchor_call) { struct pfioc_nv nv; nvlist_t *nvl; void *nvlpacked; int ret; nvl = nvlist_create(0); if (nvl == 0) return (ENOMEM); nvlist_add_number(nvl, "nr", nr); nvlist_add_number(nvl, "ticket", ticket); nvlist_add_string(nvl, "anchor", anchor); nvlist_add_number(nvl, "ruleset", ruleset); nvlpacked = nvlist_pack(nvl, &nv.len); if (nvlpacked == NULL) { nvlist_destroy(nvl); return (ENOMEM); } nv.data = malloc(8182); nv.size = 8192; assert(nv.len <= nv.size); memcpy(nv.data, nvlpacked, nv.len); nvlist_destroy(nvl); nvl = NULL; free(nvlpacked); ret = ioctl(dev, DIOCGETRULENV, &nv); if (ret != 0) { free(nv.data); return (ret); } nvl = nvlist_unpack(nv.data, nv.len, 0); if (nvl == NULL) { free(nv.data); return (EIO); } pf_nvrule_to_rule(nvlist_get_nvlist(nvl, "rule"), rule); if (anchor_call) strlcpy(anchor_call, nvlist_get_string(nvl, "anchor_call"), MAXPATHLEN); free(nv.data); nvlist_destroy(nvl); return (0); } diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h index 65ff2179f23d..996830619489 100644 --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -1,45 +1,182 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021 Rubicon Communications, LLC (Netgate) * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _PFCTL_IOCTL_H_ #define _PFCTL_IOCTL_H_ #include +struct pfctl_anchor; + +struct pfctl_rule { + struct pf_rule_addr src; + struct pf_rule_addr dst; + union pf_rule_ptr skip[PF_SKIP_COUNT]; + char label[PF_RULE_LABEL_SIZE]; + char ifname[IFNAMSIZ]; + char qname[PF_QNAME_SIZE]; + char pqname[PF_QNAME_SIZE]; + char tagname[PF_TAG_NAME_SIZE]; + char match_tagname[PF_TAG_NAME_SIZE]; + + char overload_tblname[PF_TABLE_NAME_SIZE]; + + TAILQ_ENTRY(pfctl_rule) entries; + struct pf_pool rpool; + + u_int64_t evaluations; + u_int64_t packets[2]; + u_int64_t bytes[2]; + + struct pfi_kif *kif; + struct pfctl_anchor *anchor; + struct pfr_ktable *overload_tbl; + + pf_osfp_t os_fingerprint; + + int rtableid; + u_int32_t timeout[PFTM_MAX]; + u_int32_t max_states; + u_int32_t max_src_nodes; + u_int32_t max_src_states; + u_int32_t max_src_conn; + struct { + u_int32_t limit; + u_int32_t seconds; + } max_src_conn_rate; + u_int32_t qid; + u_int32_t pqid; + u_int32_t nr; + u_int32_t prob; + uid_t cuid; + pid_t cpid; + + counter_u64_t states_cur; + counter_u64_t states_tot; + counter_u64_t src_nodes; + + u_int16_t return_icmp; + u_int16_t return_icmp6; + u_int16_t max_mss; + u_int16_t tag; + u_int16_t match_tag; + u_int16_t scrub_flags; + + struct pf_rule_uid uid; + struct pf_rule_gid gid; + + u_int32_t rule_flag; + u_int8_t action; + u_int8_t direction; + u_int8_t log; + u_int8_t logif; + u_int8_t quick; + u_int8_t ifnot; + u_int8_t match_tag_not; + u_int8_t natpass; + + u_int8_t keep_state; + sa_family_t af; + u_int8_t proto; + u_int8_t type; + u_int8_t code; + u_int8_t flags; + u_int8_t flagset; + u_int8_t min_ttl; + u_int8_t allow_opts; + u_int8_t rt; + u_int8_t return_ttl; + u_int8_t tos; + u_int8_t set_tos; + u_int8_t anchor_relative; + u_int8_t anchor_wildcard; + + u_int8_t flush; + u_int8_t prio; + u_int8_t set_prio[2]; + + struct { + struct pf_addr addr; + u_int16_t port; + } divert; + + uint64_t u_states_cur; + uint64_t u_states_tot; + uint64_t u_src_nodes; +}; + +TAILQ_HEAD(pfctl_rulequeue, pfctl_rule); + +struct pfctl_ruleset { + struct { + struct pfctl_rulequeue queues[2]; + struct { + struct pfctl_rulequeue *ptr; + struct pfctl_rule **ptr_array; + u_int32_t rcount; + u_int32_t ticket; + int open; + } active, inactive; + } rules[PF_RULESET_MAX]; + struct pfctl_anchor *anchor; + u_int32_t tticket; + int tables; + int topen; +}; + +RB_HEAD(pfctl_anchor_global, pfctl_anchor); +RB_HEAD(pfctl_anchor_node, pfctl_anchor); +struct pfctl_anchor { + RB_ENTRY(pfctl_anchor) entry_global; + RB_ENTRY(pfctl_anchor) entry_node; + struct pfctl_anchor *parent; + struct pfctl_anchor_node children; + char name[PF_ANCHOR_NAME_SIZE]; + char path[MAXPATHLEN]; + struct pfctl_ruleset ruleset; + int refcnt; /* anchor rules */ + int match; /* XXX: used for pfctl black magic */ +}; +RB_PROTOTYPE(pfctl_anchor_global, pfctl_anchor, entry_global, + pf_anchor_compare); +RB_PROTOTYPE(pfctl_anchor_node, pfctl_anchor, entry_node, + pf_anchor_compare); + int pfctl_get_rule(int dev, u_int32_t nr, u_int32_t ticket, - const char *anchor, u_int32_t ruleset, struct pf_rule *rule, + const char *anchor, u_int32_t ruleset, struct pfctl_rule *rule, char *anchor_call); -int pfctl_add_rule(int dev, const struct pf_rule *r, const char *anchor, - const char *anchor_call, u_int32_t ticket, u_int32_t pool_ticket); +int pfctl_add_rule(int dev, const struct pfctl_rule *r, + const char *anchor, const char *anchor_call, u_int32_t ticket, + u_int32_t pool_ticket); #endif diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y index 56c1f82e39a0..ff4eca03f4cf 100644 --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -1,6370 +1,6370 @@ /* $OpenBSD: parse.y,v 1.554 2008/10/17 12:59:53 henning Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Markus Friedl. All rights reserved. * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. * Copyright (c) 2001 Theo de Raadt. All rights reserved. * Copyright (c) 2002,2003 Henning Brauer. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ %{ #include __FBSDID("$FreeBSD$"); #define PFIOC_USE_LATEST #include #include #include #ifdef __FreeBSD__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" static struct pfctl *pf = NULL; static int debug = 0; static int rulestate = 0; static u_int16_t returnicmpdefault = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; static u_int16_t returnicmp6default = (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; static int blockpolicy = PFRULE_DROP; static int failpolicy = PFRULE_DROP; static int require_order = 1; static int default_statelock; static TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); static struct file { TAILQ_ENTRY(file) entry; FILE *stream; char *name; int lineno; int errors; } *file; struct file *pushfile(const char *, int); int popfile(void); int check_file_secrecy(int, const char *); int yyparse(void); int yylex(void); int yyerror(const char *, ...); int kw_cmp(const void *, const void *); int lookup(char *); int lgetc(int); int lungetc(int); int findeol(void); static TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); struct sym { TAILQ_ENTRY(sym) entry; int used; int persist; char *nam; char *val; }; int symset(const char *, const char *, int); char *symget(const char *); int atoul(char *, u_long *); enum { PFCTL_STATE_NONE, PFCTL_STATE_OPTION, PFCTL_STATE_SCRUB, PFCTL_STATE_QUEUE, PFCTL_STATE_NAT, PFCTL_STATE_FILTER }; struct node_proto { u_int8_t proto; struct node_proto *next; struct node_proto *tail; }; struct node_port { u_int16_t port[2]; u_int8_t op; struct node_port *next; struct node_port *tail; }; struct node_uid { uid_t uid[2]; u_int8_t op; struct node_uid *next; struct node_uid *tail; }; struct node_gid { gid_t gid[2]; u_int8_t op; struct node_gid *next; struct node_gid *tail; }; struct node_icmp { u_int8_t code; u_int8_t type; u_int8_t proto; struct node_icmp *next; struct node_icmp *tail; }; enum { PF_STATE_OPT_MAX, PF_STATE_OPT_NOSYNC, PF_STATE_OPT_SRCTRACK, PF_STATE_OPT_MAX_SRC_STATES, PF_STATE_OPT_MAX_SRC_CONN, PF_STATE_OPT_MAX_SRC_CONN_RATE, PF_STATE_OPT_MAX_SRC_NODES, PF_STATE_OPT_OVERLOAD, PF_STATE_OPT_STATELOCK, PF_STATE_OPT_TIMEOUT, PF_STATE_OPT_SLOPPY, }; enum { PF_SRCTRACK_NONE, PF_SRCTRACK, PF_SRCTRACK_GLOBAL, PF_SRCTRACK_RULE }; struct node_state_opt { int type; union { u_int32_t max_states; u_int32_t max_src_states; u_int32_t max_src_conn; struct { u_int32_t limit; u_int32_t seconds; } max_src_conn_rate; struct { u_int8_t flush; char tblname[PF_TABLE_NAME_SIZE]; } overload; u_int32_t max_src_nodes; u_int8_t src_track; u_int32_t statelock; struct { int number; u_int32_t seconds; } timeout; } data; struct node_state_opt *next; struct node_state_opt *tail; }; struct peer { struct node_host *host; struct node_port *port; }; static struct node_queue { char queue[PF_QNAME_SIZE]; char parent[PF_QNAME_SIZE]; char ifname[IFNAMSIZ]; int scheduler; struct node_queue *next; struct node_queue *tail; } *queues = NULL; struct node_qassign { char *qname; char *pqname; }; static struct filter_opts { int marker; #define FOM_FLAGS 0x01 #define FOM_ICMP 0x02 #define FOM_TOS 0x04 #define FOM_KEEP 0x08 #define FOM_SRCTRACK 0x10 #define FOM_SETPRIO 0x0400 #define FOM_PRIO 0x2000 struct node_uid *uid; struct node_gid *gid; struct { u_int8_t b1; u_int8_t b2; u_int16_t w; u_int16_t w2; } flags; struct node_icmp *icmpspec; u_int32_t tos; u_int32_t prob; struct { int action; struct node_state_opt *options; } keep; int fragment; int allowopts; char *label; struct node_qassign queues; char *tag; char *match_tag; u_int8_t match_tag_not; u_int rtableid; u_int8_t prio; u_int8_t set_prio[2]; struct { struct node_host *addr; u_int16_t port; } divert; } filter_opts; static struct antispoof_opts { char *label; u_int rtableid; } antispoof_opts; static struct scrub_opts { int marker; #define SOM_MINTTL 0x01 #define SOM_MAXMSS 0x02 #define SOM_FRAGCACHE 0x04 #define SOM_SETTOS 0x08 int nodf; int minttl; int maxmss; int settos; int fragcache; int randomid; int reassemble_tcp; char *match_tag; u_int8_t match_tag_not; u_int rtableid; } scrub_opts; static struct queue_opts { int marker; #define QOM_BWSPEC 0x01 #define QOM_SCHEDULER 0x02 #define QOM_PRIORITY 0x04 #define QOM_TBRSIZE 0x08 #define QOM_QLIMIT 0x10 struct node_queue_bw queue_bwspec; struct node_queue_opt scheduler; int priority; unsigned int tbrsize; int qlimit; } queue_opts; static struct table_opts { int flags; int init_addr; struct node_tinithead init_nodes; } table_opts; static struct pool_opts { int marker; #define POM_TYPE 0x01 #define POM_STICKYADDRESS 0x02 u_int8_t opts; int type; int staticport; struct pf_poolhashkey *key; } pool_opts; static struct codel_opts codel_opts; static struct node_hfsc_opts hfsc_opts; static struct node_fairq_opts fairq_opts; static struct node_state_opt *keep_state_defaults = NULL; int disallow_table(struct node_host *, const char *); int disallow_urpf_failed(struct node_host *, const char *); int disallow_alias(struct node_host *, const char *); -int rule_consistent(struct pf_rule *, int); -int filter_consistent(struct pf_rule *, int); -int nat_consistent(struct pf_rule *); -int rdr_consistent(struct pf_rule *); +int rule_consistent(struct pfctl_rule *, int); +int filter_consistent(struct pfctl_rule *, int); +int nat_consistent(struct pfctl_rule *); +int rdr_consistent(struct pfctl_rule *); int process_tabledef(char *, struct table_opts *); void expand_label_str(char *, size_t, const char *, const char *); void expand_label_if(const char *, char *, size_t, const char *); void expand_label_addr(const char *, char *, size_t, u_int8_t, struct node_host *); void expand_label_port(const char *, char *, size_t, struct node_port *); void expand_label_proto(const char *, char *, size_t, u_int8_t); void expand_label_nr(const char *, char *, size_t); void expand_label(char *, size_t, const char *, u_int8_t, struct node_host *, struct node_port *, struct node_host *, struct node_port *, u_int8_t); -void expand_rule(struct pf_rule *, struct node_if *, +void expand_rule(struct pfctl_rule *, struct node_if *, struct node_host *, struct node_proto *, struct node_os *, struct node_host *, struct node_port *, struct node_host *, struct node_port *, struct node_uid *, struct node_gid *, struct node_icmp *, const char *); int expand_altq(struct pf_altq *, struct node_if *, struct node_queue *, struct node_queue_bw bwspec, struct node_queue_opt *); int expand_queue(struct pf_altq *, struct node_if *, struct node_queue *, struct node_queue_bw, struct node_queue_opt *); int expand_skip_interface(struct node_if *); int check_rulestate(int); int getservice(char *); -int rule_label(struct pf_rule *, char *); +int rule_label(struct pfctl_rule *, char *); int rt_tableid_max(void); -void mv_rules(struct pf_ruleset *, struct pf_ruleset *); +void mv_rules(struct pfctl_ruleset *, struct pfctl_ruleset *); void decide_address_family(struct node_host *, sa_family_t *); void remove_invalid_hosts(struct node_host **, sa_family_t *); int invalid_redirect(struct node_host *, sa_family_t); u_int16_t parseicmpspec(char *, sa_family_t); int kw_casecmp(const void *, const void *); int map_tos(char *string, int *); static TAILQ_HEAD(loadanchorshead, loadanchors) loadanchorshead = TAILQ_HEAD_INITIALIZER(loadanchorshead); struct loadanchors { TAILQ_ENTRY(loadanchors) entries; char *anchorname; char *filename; }; typedef struct { union { int64_t number; double probability; int i; char *string; u_int rtableid; struct { u_int8_t b1; u_int8_t b2; u_int16_t w; u_int16_t w2; } b; struct range { int a; int b; int t; } range; struct node_if *interface; struct node_proto *proto; struct node_icmp *icmp; struct node_host *host; struct node_os *os; struct node_port *port; struct node_uid *uid; struct node_gid *gid; struct node_state_opt *state_opt; struct peer peer; struct { struct peer src, dst; struct node_os *src_os; } fromto; struct { struct node_host *host; u_int8_t rt; u_int8_t pool_opts; sa_family_t af; struct pf_poolhashkey *key; } route; struct redirection { struct node_host *host; struct range rport; } *redirection; struct { int action; struct node_state_opt *options; } keep_state; struct { u_int8_t log; u_int8_t logif; u_int8_t quick; } logquick; struct { int neg; char *name; } tagged; struct pf_poolhashkey *hashkey; struct node_queue *queue; struct node_queue_opt queue_options; struct node_queue_bw queue_bwspec; struct node_qassign qassign; struct filter_opts filter_opts; struct antispoof_opts antispoof_opts; struct queue_opts queue_opts; struct scrub_opts scrub_opts; struct table_opts table_opts; struct pool_opts pool_opts; struct node_hfsc_opts hfsc_opts; struct node_fairq_opts fairq_opts; struct codel_opts codel_opts; } v; int lineno; } YYSTYPE; #define PPORT_RANGE 1 #define PPORT_STAR 2 int parseport(char *, struct range *r, int); #define DYNIF_MULTIADDR(addr) ((addr).type == PF_ADDR_DYNIFTL && \ (!((addr).iflags & PFI_AFLAG_NOALIAS) || \ !isdigit((addr).v.ifname[strlen((addr).v.ifname)-1]))) %} %token PASS BLOCK SCRUB RETURN IN OS OUT LOG QUICK ON FROM TO FLAGS %token RETURNRST RETURNICMP RETURNICMP6 PROTO INET INET6 ALL ANY ICMPTYPE %token ICMP6TYPE CODE KEEP MODULATE STATE PORT RDR NAT BINAT ARROW NODF %token MINTTL ERROR ALLOWOPTS FASTROUTE FILENAME ROUTETO DUPTO REPLYTO NO LABEL %token NOROUTE URPFFAILED FRAGMENT USER GROUP MAXMSS MAXIMUM TTL TOS DROP TABLE %token REASSEMBLE FRAGDROP FRAGCROP ANCHOR NATANCHOR RDRANCHOR BINATANCHOR %token SET OPTIMIZATION TIMEOUT LIMIT LOGINTERFACE BLOCKPOLICY FAILPOLICY %token RANDOMID REQUIREORDER SYNPROXY FINGERPRINTS NOSYNC DEBUG SKIP HOSTID %token ANTISPOOF FOR INCLUDE %token BITMASK RANDOM SOURCEHASH ROUNDROBIN STATICPORT PROBABILITY %token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME %token UPPERLIMIT QUEUE PRIORITY QLIMIT HOGS BUCKETS RTABLE TARGET INTERVAL %token LOAD RULESET_OPTIMIZATION PRIO %token STICKYADDRESS MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE %token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY %token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS %token DIVERTTO DIVERTREPLY %token STRING %token NUMBER %token PORTBINARY %type interface if_list if_item_not if_item %type number icmptype icmp6type uid gid %type tos not yesno %type probability %type no dir af fragcache optimizer %type sourcetrack flush unaryop statelock %type action nataction natpasslog scrubaction %type flags flag blockspec prio %type portplain portstar portrange %type hashkey %type proto proto_list proto_item %type protoval %type icmpspec %type icmp_list icmp_item %type icmp6_list icmp6_item %type reticmpspec reticmp6spec %type fromto %type ipportspec from to %type ipspec toipspec xhost host dynaddr host_list %type redir_host_list redirspec %type route_host route_host_list routespec %type os xos os_list %type portspec port_list port_item %type uids uid_list uid_item %type gids gid_list gid_item %type route %type redirection redirpool %type label stringall tag anchorname %type string varstring numberstring %type keep %type state_opt_spec state_opt_list state_opt_item %type logquick quick log logopts logopt %type antispoof_ifspc antispoof_iflst antispoof_if %type qname %type qassign qassign_list qassign_item %type scheduler %type cbqflags_list cbqflags_item %type priqflags_list priqflags_item %type hfscopts_list hfscopts_item hfsc_opts %type fairqopts_list fairqopts_item fairq_opts %type codelopts_list codelopts_item codel_opts %type bandwidth %type filter_opts filter_opt filter_opts_l %type filter_sets filter_set filter_sets_l %type antispoof_opts antispoof_opt antispoof_opts_l %type queue_opts queue_opt queue_opts_l %type scrub_opts scrub_opt scrub_opts_l %type table_opts table_opt table_opts_l %type pool_opts pool_opt pool_opts_l %type tagged %type rtable %% ruleset : /* empty */ | ruleset include '\n' | ruleset '\n' | ruleset option '\n' | ruleset scrubrule '\n' | ruleset natrule '\n' | ruleset binatrule '\n' | ruleset pfrule '\n' | ruleset anchorrule '\n' | ruleset loadrule '\n' | ruleset altqif '\n' | ruleset queuespec '\n' | ruleset varset '\n' | ruleset antispoof '\n' | ruleset tabledef '\n' | '{' fakeanchor '}' '\n'; | ruleset error '\n' { file->errors++; } ; include : INCLUDE STRING { struct file *nfile; if ((nfile = pushfile($2, 0)) == NULL) { yyerror("failed to include file %s", $2); free($2); YYERROR; } free($2); file = nfile; lungetc('\n'); } ; /* * apply to previouslys specified rule: must be careful to note * what that is: pf or nat or binat or rdr */ fakeanchor : fakeanchor '\n' | fakeanchor anchorrule '\n' | fakeanchor binatrule '\n' | fakeanchor natrule '\n' | fakeanchor pfrule '\n' | fakeanchor error '\n' ; optimizer : string { if (!strcmp($1, "none")) $$ = 0; else if (!strcmp($1, "basic")) $$ = PF_OPTIMIZE_BASIC; else if (!strcmp($1, "profile")) $$ = PF_OPTIMIZE_BASIC | PF_OPTIMIZE_PROFILE; else { yyerror("unknown ruleset-optimization %s", $1); YYERROR; } } ; option : SET OPTIMIZATION STRING { if (check_rulestate(PFCTL_STATE_OPTION)) { free($3); YYERROR; } if (pfctl_set_optimization(pf, $3) != 0) { yyerror("unknown optimization %s", $3); free($3); YYERROR; } free($3); } | SET RULESET_OPTIMIZATION optimizer { if (!(pf->opts & PF_OPT_OPTIMIZE)) { pf->opts |= PF_OPT_OPTIMIZE; pf->optimize = $3; } } | SET TIMEOUT timeout_spec | SET TIMEOUT '{' optnl timeout_list '}' | SET LIMIT limit_spec | SET LIMIT '{' optnl limit_list '}' | SET LOGINTERFACE stringall { if (check_rulestate(PFCTL_STATE_OPTION)) { free($3); YYERROR; } if (pfctl_set_logif(pf, $3) != 0) { yyerror("error setting loginterface %s", $3); free($3); YYERROR; } free($3); } | SET HOSTID number { if ($3 == 0 || $3 > UINT_MAX) { yyerror("hostid must be non-zero"); YYERROR; } if (pfctl_set_hostid(pf, $3) != 0) { yyerror("error setting hostid %08x", $3); YYERROR; } } | SET BLOCKPOLICY DROP { if (pf->opts & PF_OPT_VERBOSE) printf("set block-policy drop\n"); if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; blockpolicy = PFRULE_DROP; } | SET BLOCKPOLICY RETURN { if (pf->opts & PF_OPT_VERBOSE) printf("set block-policy return\n"); if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; blockpolicy = PFRULE_RETURN; } | SET FAILPOLICY DROP { if (pf->opts & PF_OPT_VERBOSE) printf("set fail-policy drop\n"); if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; failpolicy = PFRULE_DROP; } | SET FAILPOLICY RETURN { if (pf->opts & PF_OPT_VERBOSE) printf("set fail-policy return\n"); if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; failpolicy = PFRULE_RETURN; } | SET REQUIREORDER yesno { if (pf->opts & PF_OPT_VERBOSE) printf("set require-order %s\n", $3 == 1 ? "yes" : "no"); require_order = $3; } | SET FINGERPRINTS STRING { if (pf->opts & PF_OPT_VERBOSE) printf("set fingerprints \"%s\"\n", $3); if (check_rulestate(PFCTL_STATE_OPTION)) { free($3); YYERROR; } if (!pf->anchor->name[0]) { if (pfctl_file_fingerprints(pf->dev, pf->opts, $3)) { yyerror("error loading " "fingerprints %s", $3); free($3); YYERROR; } } free($3); } | SET STATEPOLICY statelock { if (pf->opts & PF_OPT_VERBOSE) switch ($3) { case 0: printf("set state-policy floating\n"); break; case PFRULE_IFBOUND: printf("set state-policy if-bound\n"); break; } default_statelock = $3; } | SET DEBUG STRING { if (check_rulestate(PFCTL_STATE_OPTION)) { free($3); YYERROR; } if (pfctl_set_debug(pf, $3) != 0) { yyerror("error setting debuglevel %s", $3); free($3); YYERROR; } free($3); } | SET SKIP interface { if (expand_skip_interface($3) != 0) { yyerror("error setting skip interface(s)"); YYERROR; } } | SET STATEDEFAULTS state_opt_list { if (keep_state_defaults != NULL) { yyerror("cannot redefine state-defaults"); YYERROR; } keep_state_defaults = $3; } ; stringall : STRING { $$ = $1; } | ALL { if (($$ = strdup("all")) == NULL) { err(1, "stringall: strdup"); } } ; string : STRING string { if (asprintf(&$$, "%s %s", $1, $2) == -1) err(1, "string: asprintf"); free($1); free($2); } | STRING ; varstring : numberstring varstring { if (asprintf(&$$, "%s %s", $1, $2) == -1) err(1, "string: asprintf"); free($1); free($2); } | numberstring ; numberstring : NUMBER { char *s; if (asprintf(&s, "%lld", (long long)$1) == -1) { yyerror("string: asprintf"); YYERROR; } $$ = s; } | STRING ; varset : STRING '=' varstring { if (pf->opts & PF_OPT_VERBOSE) printf("%s = \"%s\"\n", $1, $3); if (symset($1, $3, 0) == -1) err(1, "cannot store variable %s", $1); free($1); free($3); } ; anchorname : STRING { $$ = $1; } | /* empty */ { $$ = NULL; } ; pfa_anchorlist : /* empty */ | pfa_anchorlist '\n' | pfa_anchorlist pfrule '\n' | pfa_anchorlist anchorrule '\n' ; pfa_anchor : '{' { char ta[PF_ANCHOR_NAME_SIZE]; - struct pf_ruleset *rs; + struct pfctl_ruleset *rs; /* steping into a brace anchor */ pf->asd++; pf->bn++; pf->brace = 1; /* create a holding ruleset in the root */ snprintf(ta, PF_ANCHOR_NAME_SIZE, "_%d", pf->bn); rs = pf_find_or_create_ruleset(ta); if (rs == NULL) err(1, "pfa_anchor: pf_find_or_create_ruleset"); pf->astack[pf->asd] = rs->anchor; pf->anchor = rs->anchor; } '\n' pfa_anchorlist '}' { pf->alast = pf->anchor; pf->asd--; pf->anchor = pf->astack[pf->asd]; } | /* empty */ ; anchorrule : ANCHOR anchorname dir quick interface af proto fromto filter_opts pfa_anchor { - struct pf_rule r; + struct pfctl_rule r; struct node_proto *proto; if (check_rulestate(PFCTL_STATE_FILTER)) { if ($2) free($2); YYERROR; } if ($2 && ($2[0] == '_' || strstr($2, "/_") != NULL)) { free($2); yyerror("anchor names beginning with '_' " "are reserved for internal use"); YYERROR; } memset(&r, 0, sizeof(r)); if (pf->astack[pf->asd + 1]) { /* move inline rules into relative location */ - pf_anchor_setup(&r, + pfctl_anchor_setup(&r, &pf->astack[pf->asd]->ruleset, $2 ? $2 : pf->alast->name); if (r.anchor == NULL) err(1, "anchorrule: unable to " "create ruleset"); if (pf->alast != r.anchor) { if (r.anchor->match) { yyerror("inline anchor '%s' " "already exists", r.anchor->name); YYERROR; } mv_rules(&pf->alast->ruleset, &r.anchor->ruleset); } pf_remove_if_empty_ruleset(&pf->alast->ruleset); pf->alast = r.anchor; } else { if (!$2) { yyerror("anchors without explicit " "rules must specify a name"); YYERROR; } } r.direction = $3; r.quick = $4.quick; r.af = $6; r.prob = $9.prob; r.rtableid = $9.rtableid; if ($9.tag) if (strlcpy(r.tagname, $9.tag, PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } if ($9.match_tag) if (strlcpy(r.match_tagname, $9.match_tag, PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } r.match_tag_not = $9.match_tag_not; if (rule_label(&r, $9.label)) YYERROR; free($9.label); r.flags = $9.flags.b1; r.flagset = $9.flags.b2; if (($9.flags.b1 & $9.flags.b2) != $9.flags.b1) { yyerror("flags always false"); YYERROR; } if ($9.flags.b1 || $9.flags.b2 || $8.src_os) { for (proto = $7; proto != NULL && proto->proto != IPPROTO_TCP; proto = proto->next) ; /* nothing */ if (proto == NULL && $7 != NULL) { if ($9.flags.b1 || $9.flags.b2) yyerror( "flags only apply to tcp"); if ($8.src_os) yyerror( "OS fingerprinting only " "applies to tcp"); YYERROR; } } r.tos = $9.tos; if ($9.keep.action) { yyerror("cannot specify state handling " "on anchors"); YYERROR; } if ($9.match_tag) if (strlcpy(r.match_tagname, $9.match_tag, PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } r.match_tag_not = $9.match_tag_not; if ($9.marker & FOM_PRIO) { if ($9.prio == 0) r.prio = PF_PRIO_ZERO; else r.prio = $9.prio; } if ($9.marker & FOM_SETPRIO) { r.set_prio[0] = $9.set_prio[0]; r.set_prio[1] = $9.set_prio[1]; r.scrub_flags |= PFSTATE_SETPRIO; } decide_address_family($8.src.host, &r.af); decide_address_family($8.dst.host, &r.af); expand_rule(&r, $5, NULL, $7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host, $8.dst.port, $9.uid, $9.gid, $9.icmpspec, pf->astack[pf->asd + 1] ? pf->alast->name : $2); free($2); pf->astack[pf->asd + 1] = NULL; } | NATANCHOR string interface af proto fromto rtable { - struct pf_rule r; + struct pfctl_rule r; if (check_rulestate(PFCTL_STATE_NAT)) { free($2); YYERROR; } memset(&r, 0, sizeof(r)); r.action = PF_NAT; r.af = $4; r.rtableid = $7; decide_address_family($6.src.host, &r.af); decide_address_family($6.dst.host, &r.af); expand_rule(&r, $3, NULL, $5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host, $6.dst.port, 0, 0, 0, $2); free($2); } | RDRANCHOR string interface af proto fromto rtable { - struct pf_rule r; + struct pfctl_rule r; if (check_rulestate(PFCTL_STATE_NAT)) { free($2); YYERROR; } memset(&r, 0, sizeof(r)); r.action = PF_RDR; r.af = $4; r.rtableid = $7; decide_address_family($6.src.host, &r.af); decide_address_family($6.dst.host, &r.af); if ($6.src.port != NULL) { yyerror("source port parameter not supported" " in rdr-anchor"); YYERROR; } if ($6.dst.port != NULL) { if ($6.dst.port->next != NULL) { yyerror("destination port list " "expansion not supported in " "rdr-anchor"); YYERROR; } else if ($6.dst.port->op != PF_OP_EQ) { yyerror("destination port operators" " not supported in rdr-anchor"); YYERROR; } r.dst.port[0] = $6.dst.port->port[0]; r.dst.port[1] = $6.dst.port->port[1]; r.dst.port_op = $6.dst.port->op; } expand_rule(&r, $3, NULL, $5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host, $6.dst.port, 0, 0, 0, $2); free($2); } | BINATANCHOR string interface af proto fromto rtable { - struct pf_rule r; + struct pfctl_rule r; if (check_rulestate(PFCTL_STATE_NAT)) { free($2); YYERROR; } memset(&r, 0, sizeof(r)); r.action = PF_BINAT; r.af = $4; r.rtableid = $7; if ($5 != NULL) { if ($5->next != NULL) { yyerror("proto list expansion" " not supported in binat-anchor"); YYERROR; } r.proto = $5->proto; free($5); } if ($6.src.host != NULL || $6.src.port != NULL || $6.dst.host != NULL || $6.dst.port != NULL) { yyerror("fromto parameter not supported" " in binat-anchor"); YYERROR; } decide_address_family($6.src.host, &r.af); decide_address_family($6.dst.host, &r.af); pfctl_append_rule(pf, &r, $2); free($2); } ; loadrule : LOAD ANCHOR string FROM string { struct loadanchors *loadanchor; if (strlen(pf->anchor->name) + 1 + strlen($3) >= MAXPATHLEN) { yyerror("anchorname %s too long, max %u\n", $3, MAXPATHLEN - 1); free($3); YYERROR; } loadanchor = calloc(1, sizeof(struct loadanchors)); if (loadanchor == NULL) err(1, "loadrule: calloc"); if ((loadanchor->anchorname = malloc(MAXPATHLEN)) == NULL) err(1, "loadrule: malloc"); if (pf->anchor->name[0]) snprintf(loadanchor->anchorname, MAXPATHLEN, "%s/%s", pf->anchor->name, $3); else strlcpy(loadanchor->anchorname, $3, MAXPATHLEN); if ((loadanchor->filename = strdup($5)) == NULL) err(1, "loadrule: strdup"); TAILQ_INSERT_TAIL(&loadanchorshead, loadanchor, entries); free($3); free($5); }; scrubaction : no SCRUB { $$.b2 = $$.w = 0; if ($1) $$.b1 = PF_NOSCRUB; else $$.b1 = PF_SCRUB; } ; scrubrule : scrubaction dir logquick interface af proto fromto scrub_opts { - struct pf_rule r; + struct pfctl_rule r; if (check_rulestate(PFCTL_STATE_SCRUB)) YYERROR; memset(&r, 0, sizeof(r)); r.action = $1.b1; r.direction = $2; r.log = $3.log; r.logif = $3.logif; if ($3.quick) { yyerror("scrub rules do not support 'quick'"); YYERROR; } r.af = $5; if ($8.nodf) r.rule_flag |= PFRULE_NODF; if ($8.randomid) r.rule_flag |= PFRULE_RANDOMID; if ($8.reassemble_tcp) { if (r.direction != PF_INOUT) { yyerror("reassemble tcp rules can not " "specify direction"); YYERROR; } r.rule_flag |= PFRULE_REASSEMBLE_TCP; } if ($8.minttl) r.min_ttl = $8.minttl; if ($8.maxmss) r.max_mss = $8.maxmss; if ($8.marker & SOM_SETTOS) { r.rule_flag |= PFRULE_SET_TOS; r.set_tos = $8.settos; } if ($8.fragcache) r.rule_flag |= $8.fragcache; if ($8.match_tag) if (strlcpy(r.match_tagname, $8.match_tag, PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } r.match_tag_not = $8.match_tag_not; r.rtableid = $8.rtableid; expand_rule(&r, $4, NULL, $6, $7.src_os, $7.src.host, $7.src.port, $7.dst.host, $7.dst.port, NULL, NULL, NULL, ""); } ; scrub_opts : { bzero(&scrub_opts, sizeof scrub_opts); scrub_opts.rtableid = -1; } scrub_opts_l { $$ = scrub_opts; } | /* empty */ { bzero(&scrub_opts, sizeof scrub_opts); scrub_opts.rtableid = -1; $$ = scrub_opts; } ; scrub_opts_l : scrub_opts_l scrub_opt | scrub_opt ; scrub_opt : NODF { if (scrub_opts.nodf) { yyerror("no-df cannot be respecified"); YYERROR; } scrub_opts.nodf = 1; } | MINTTL NUMBER { if (scrub_opts.marker & SOM_MINTTL) { yyerror("min-ttl cannot be respecified"); YYERROR; } if ($2 < 0 || $2 > 255) { yyerror("illegal min-ttl value %d", $2); YYERROR; } scrub_opts.marker |= SOM_MINTTL; scrub_opts.minttl = $2; } | MAXMSS NUMBER { if (scrub_opts.marker & SOM_MAXMSS) { yyerror("max-mss cannot be respecified"); YYERROR; } if ($2 < 0 || $2 > 65535) { yyerror("illegal max-mss value %d", $2); YYERROR; } scrub_opts.marker |= SOM_MAXMSS; scrub_opts.maxmss = $2; } | SETTOS tos { if (scrub_opts.marker & SOM_SETTOS) { yyerror("set-tos cannot be respecified"); YYERROR; } scrub_opts.marker |= SOM_SETTOS; scrub_opts.settos = $2; } | fragcache { if (scrub_opts.marker & SOM_FRAGCACHE) { yyerror("fragcache cannot be respecified"); YYERROR; } scrub_opts.marker |= SOM_FRAGCACHE; scrub_opts.fragcache = $1; } | REASSEMBLE STRING { if (strcasecmp($2, "tcp") != 0) { yyerror("scrub reassemble supports only tcp, " "not '%s'", $2); free($2); YYERROR; } free($2); if (scrub_opts.reassemble_tcp) { yyerror("reassemble tcp cannot be respecified"); YYERROR; } scrub_opts.reassemble_tcp = 1; } | RANDOMID { if (scrub_opts.randomid) { yyerror("random-id cannot be respecified"); YYERROR; } scrub_opts.randomid = 1; } | RTABLE NUMBER { if ($2 < 0 || $2 > rt_tableid_max()) { yyerror("invalid rtable id"); YYERROR; } scrub_opts.rtableid = $2; } | not TAGGED string { scrub_opts.match_tag = $3; scrub_opts.match_tag_not = $1; } ; fragcache : FRAGMENT REASSEMBLE { $$ = 0; /* default */ } | FRAGMENT FRAGCROP { $$ = 0; } | FRAGMENT FRAGDROP { $$ = 0; } ; antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts { - struct pf_rule r; + struct pfctl_rule r; struct node_host *h = NULL, *hh; struct node_if *i, *j; if (check_rulestate(PFCTL_STATE_FILTER)) YYERROR; for (i = $3; i; i = i->next) { bzero(&r, sizeof(r)); r.action = PF_DROP; r.direction = PF_IN; r.log = $2.log; r.logif = $2.logif; r.quick = $2.quick; r.af = $4; if (rule_label(&r, $5.label)) YYERROR; r.rtableid = $5.rtableid; j = calloc(1, sizeof(struct node_if)); if (j == NULL) err(1, "antispoof: calloc"); if (strlcpy(j->ifname, i->ifname, sizeof(j->ifname)) >= sizeof(j->ifname)) { free(j); yyerror("interface name too long"); YYERROR; } j->not = 1; if (i->dynamic) { h = calloc(1, sizeof(*h)); if (h == NULL) err(1, "address: calloc"); h->addr.type = PF_ADDR_DYNIFTL; set_ipmask(h, 128); if (strlcpy(h->addr.v.ifname, i->ifname, sizeof(h->addr.v.ifname)) >= sizeof(h->addr.v.ifname)) { free(h); yyerror( "interface name too long"); YYERROR; } hh = malloc(sizeof(*hh)); if (hh == NULL) err(1, "address: malloc"); bcopy(h, hh, sizeof(*hh)); h->addr.iflags = PFI_AFLAG_NETWORK; } else { h = ifa_lookup(j->ifname, PFI_AFLAG_NETWORK); hh = NULL; } if (h != NULL) expand_rule(&r, j, NULL, NULL, NULL, h, NULL, NULL, NULL, NULL, NULL, NULL, ""); if ((i->ifa_flags & IFF_LOOPBACK) == 0) { bzero(&r, sizeof(r)); r.action = PF_DROP; r.direction = PF_IN; r.log = $2.log; r.logif = $2.logif; r.quick = $2.quick; r.af = $4; if (rule_label(&r, $5.label)) YYERROR; r.rtableid = $5.rtableid; if (hh != NULL) h = hh; else h = ifa_lookup(i->ifname, 0); if (h != NULL) expand_rule(&r, NULL, NULL, NULL, NULL, h, NULL, NULL, NULL, NULL, NULL, NULL, ""); } else free(hh); } free($5.label); } ; antispoof_ifspc : FOR antispoof_if { $$ = $2; } | FOR '{' optnl antispoof_iflst '}' { $$ = $4; } ; antispoof_iflst : antispoof_if optnl { $$ = $1; } | antispoof_iflst comma antispoof_if optnl { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; antispoof_if : if_item { $$ = $1; } | '(' if_item ')' { $2->dynamic = 1; $$ = $2; } ; antispoof_opts : { bzero(&antispoof_opts, sizeof antispoof_opts); antispoof_opts.rtableid = -1; } antispoof_opts_l { $$ = antispoof_opts; } | /* empty */ { bzero(&antispoof_opts, sizeof antispoof_opts); antispoof_opts.rtableid = -1; $$ = antispoof_opts; } ; antispoof_opts_l : antispoof_opts_l antispoof_opt | antispoof_opt ; antispoof_opt : label { if (antispoof_opts.label) { yyerror("label cannot be redefined"); YYERROR; } antispoof_opts.label = $1; } | RTABLE NUMBER { if ($2 < 0 || $2 > rt_tableid_max()) { yyerror("invalid rtable id"); YYERROR; } antispoof_opts.rtableid = $2; } ; not : '!' { $$ = 1; } | /* empty */ { $$ = 0; } ; tabledef : TABLE '<' STRING '>' table_opts { struct node_host *h, *nh; struct node_tinit *ti, *nti; if (strlen($3) >= PF_TABLE_NAME_SIZE) { yyerror("table name too long, max %d chars", PF_TABLE_NAME_SIZE - 1); free($3); YYERROR; } if (pf->loadopt & PFCTL_FLAG_TABLE) if (process_tabledef($3, &$5)) { free($3); YYERROR; } free($3); for (ti = SIMPLEQ_FIRST(&$5.init_nodes); ti != SIMPLEQ_END(&$5.init_nodes); ti = nti) { if (ti->file) free(ti->file); for (h = ti->host; h != NULL; h = nh) { nh = h->next; free(h); } nti = SIMPLEQ_NEXT(ti, entries); free(ti); } } ; table_opts : { bzero(&table_opts, sizeof table_opts); SIMPLEQ_INIT(&table_opts.init_nodes); } table_opts_l { $$ = table_opts; } | /* empty */ { bzero(&table_opts, sizeof table_opts); SIMPLEQ_INIT(&table_opts.init_nodes); $$ = table_opts; } ; table_opts_l : table_opts_l table_opt | table_opt ; table_opt : STRING { if (!strcmp($1, "const")) table_opts.flags |= PFR_TFLAG_CONST; else if (!strcmp($1, "persist")) table_opts.flags |= PFR_TFLAG_PERSIST; else if (!strcmp($1, "counters")) table_opts.flags |= PFR_TFLAG_COUNTERS; else { yyerror("invalid table option '%s'", $1); free($1); YYERROR; } free($1); } | '{' optnl '}' { table_opts.init_addr = 1; } | '{' optnl host_list '}' { struct node_host *n; struct node_tinit *ti; for (n = $3; n != NULL; n = n->next) { switch (n->addr.type) { case PF_ADDR_ADDRMASK: continue; /* ok */ case PF_ADDR_RANGE: yyerror("address ranges are not " "permitted inside tables"); break; case PF_ADDR_DYNIFTL: yyerror("dynamic addresses are not " "permitted inside tables"); break; case PF_ADDR_TABLE: yyerror("tables cannot contain tables"); break; case PF_ADDR_NOROUTE: yyerror("\"no-route\" is not permitted " "inside tables"); break; case PF_ADDR_URPFFAILED: yyerror("\"urpf-failed\" is not " "permitted inside tables"); break; default: yyerror("unknown address type %d", n->addr.type); } YYERROR; } if (!(ti = calloc(1, sizeof(*ti)))) err(1, "table_opt: calloc"); ti->host = $3; SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti, entries); table_opts.init_addr = 1; } | FILENAME STRING { struct node_tinit *ti; if (!(ti = calloc(1, sizeof(*ti)))) err(1, "table_opt: calloc"); ti->file = $2; SIMPLEQ_INSERT_TAIL(&table_opts.init_nodes, ti, entries); table_opts.init_addr = 1; } ; altqif : ALTQ interface queue_opts QUEUE qassign { struct pf_altq a; if (check_rulestate(PFCTL_STATE_QUEUE)) YYERROR; memset(&a, 0, sizeof(a)); if ($3.scheduler.qtype == ALTQT_NONE) { yyerror("no scheduler specified!"); YYERROR; } a.scheduler = $3.scheduler.qtype; a.qlimit = $3.qlimit; a.tbrsize = $3.tbrsize; if ($5 == NULL && $3.scheduler.qtype != ALTQT_CODEL) { yyerror("no child queues specified"); YYERROR; } if (expand_altq(&a, $2, $5, $3.queue_bwspec, &$3.scheduler)) YYERROR; } ; queuespec : QUEUE STRING interface queue_opts qassign { struct pf_altq a; if (check_rulestate(PFCTL_STATE_QUEUE)) { free($2); YYERROR; } memset(&a, 0, sizeof(a)); if (strlcpy(a.qname, $2, sizeof(a.qname)) >= sizeof(a.qname)) { yyerror("queue name too long (max " "%d chars)", PF_QNAME_SIZE-1); free($2); YYERROR; } free($2); if ($4.tbrsize) { yyerror("cannot specify tbrsize for queue"); YYERROR; } if ($4.priority > 255) { yyerror("priority out of range: max 255"); YYERROR; } a.priority = $4.priority; a.qlimit = $4.qlimit; a.scheduler = $4.scheduler.qtype; if (expand_queue(&a, $3, $5, $4.queue_bwspec, &$4.scheduler)) { yyerror("errors in queue definition"); YYERROR; } } ; queue_opts : { bzero(&queue_opts, sizeof queue_opts); queue_opts.priority = DEFAULT_PRIORITY; queue_opts.qlimit = DEFAULT_QLIMIT; queue_opts.scheduler.qtype = ALTQT_NONE; queue_opts.queue_bwspec.bw_percent = 100; } queue_opts_l { $$ = queue_opts; } | /* empty */ { bzero(&queue_opts, sizeof queue_opts); queue_opts.priority = DEFAULT_PRIORITY; queue_opts.qlimit = DEFAULT_QLIMIT; queue_opts.scheduler.qtype = ALTQT_NONE; queue_opts.queue_bwspec.bw_percent = 100; $$ = queue_opts; } ; queue_opts_l : queue_opts_l queue_opt | queue_opt ; queue_opt : BANDWIDTH bandwidth { if (queue_opts.marker & QOM_BWSPEC) { yyerror("bandwidth cannot be respecified"); YYERROR; } queue_opts.marker |= QOM_BWSPEC; queue_opts.queue_bwspec = $2; } | PRIORITY NUMBER { if (queue_opts.marker & QOM_PRIORITY) { yyerror("priority cannot be respecified"); YYERROR; } if ($2 < 0 || $2 > 255) { yyerror("priority out of range: max 255"); YYERROR; } queue_opts.marker |= QOM_PRIORITY; queue_opts.priority = $2; } | QLIMIT NUMBER { if (queue_opts.marker & QOM_QLIMIT) { yyerror("qlimit cannot be respecified"); YYERROR; } if ($2 < 0 || $2 > 65535) { yyerror("qlimit out of range: max 65535"); YYERROR; } queue_opts.marker |= QOM_QLIMIT; queue_opts.qlimit = $2; } | scheduler { if (queue_opts.marker & QOM_SCHEDULER) { yyerror("scheduler cannot be respecified"); YYERROR; } queue_opts.marker |= QOM_SCHEDULER; queue_opts.scheduler = $1; } | TBRSIZE NUMBER { if (queue_opts.marker & QOM_TBRSIZE) { yyerror("tbrsize cannot be respecified"); YYERROR; } if ($2 < 0 || $2 > UINT_MAX) { yyerror("tbrsize too big: max %u", UINT_MAX); YYERROR; } queue_opts.marker |= QOM_TBRSIZE; queue_opts.tbrsize = $2; } ; bandwidth : STRING { double bps; char *cp; $$.bw_percent = 0; bps = strtod($1, &cp); if (cp != NULL) { if (strlen(cp) > 1) { char *cu = cp + 1; if (!strcmp(cu, "Bit") || !strcmp(cu, "B") || !strcmp(cu, "bit") || !strcmp(cu, "b")) { *cu = 0; } } if (!strcmp(cp, "b")) ; /* nothing */ else if (!strcmp(cp, "K")) bps *= 1000; else if (!strcmp(cp, "M")) bps *= 1000 * 1000; else if (!strcmp(cp, "G")) bps *= 1000 * 1000 * 1000; else if (!strcmp(cp, "%")) { if (bps < 0 || bps > 100) { yyerror("bandwidth spec " "out of range"); free($1); YYERROR; } $$.bw_percent = bps; bps = 0; } else { yyerror("unknown unit %s", cp); free($1); YYERROR; } } free($1); $$.bw_absolute = (u_int64_t)bps; } | NUMBER { if ($1 < 0 || $1 >= LLONG_MAX) { yyerror("bandwidth number too big"); YYERROR; } $$.bw_percent = 0; $$.bw_absolute = $1; } ; scheduler : CBQ { $$.qtype = ALTQT_CBQ; $$.data.cbq_opts.flags = 0; } | CBQ '(' cbqflags_list ')' { $$.qtype = ALTQT_CBQ; $$.data.cbq_opts.flags = $3; } | PRIQ { $$.qtype = ALTQT_PRIQ; $$.data.priq_opts.flags = 0; } | PRIQ '(' priqflags_list ')' { $$.qtype = ALTQT_PRIQ; $$.data.priq_opts.flags = $3; } | HFSC { $$.qtype = ALTQT_HFSC; bzero(&$$.data.hfsc_opts, sizeof(struct node_hfsc_opts)); } | HFSC '(' hfsc_opts ')' { $$.qtype = ALTQT_HFSC; $$.data.hfsc_opts = $3; } | FAIRQ { $$.qtype = ALTQT_FAIRQ; bzero(&$$.data.fairq_opts, sizeof(struct node_fairq_opts)); } | FAIRQ '(' fairq_opts ')' { $$.qtype = ALTQT_FAIRQ; $$.data.fairq_opts = $3; } | CODEL { $$.qtype = ALTQT_CODEL; bzero(&$$.data.codel_opts, sizeof(struct codel_opts)); } | CODEL '(' codel_opts ')' { $$.qtype = ALTQT_CODEL; $$.data.codel_opts = $3; } ; cbqflags_list : cbqflags_item { $$ |= $1; } | cbqflags_list comma cbqflags_item { $$ |= $3; } ; cbqflags_item : STRING { if (!strcmp($1, "default")) $$ = CBQCLF_DEFCLASS; else if (!strcmp($1, "borrow")) $$ = CBQCLF_BORROW; else if (!strcmp($1, "red")) $$ = CBQCLF_RED; else if (!strcmp($1, "ecn")) $$ = CBQCLF_RED|CBQCLF_ECN; else if (!strcmp($1, "rio")) $$ = CBQCLF_RIO; else if (!strcmp($1, "codel")) $$ = CBQCLF_CODEL; else { yyerror("unknown cbq flag \"%s\"", $1); free($1); YYERROR; } free($1); } ; priqflags_list : priqflags_item { $$ |= $1; } | priqflags_list comma priqflags_item { $$ |= $3; } ; priqflags_item : STRING { if (!strcmp($1, "default")) $$ = PRCF_DEFAULTCLASS; else if (!strcmp($1, "red")) $$ = PRCF_RED; else if (!strcmp($1, "ecn")) $$ = PRCF_RED|PRCF_ECN; else if (!strcmp($1, "rio")) $$ = PRCF_RIO; else if (!strcmp($1, "codel")) $$ = PRCF_CODEL; else { yyerror("unknown priq flag \"%s\"", $1); free($1); YYERROR; } free($1); } ; hfsc_opts : { bzero(&hfsc_opts, sizeof(struct node_hfsc_opts)); } hfscopts_list { $$ = hfsc_opts; } ; hfscopts_list : hfscopts_item | hfscopts_list comma hfscopts_item ; hfscopts_item : LINKSHARE bandwidth { if (hfsc_opts.linkshare.used) { yyerror("linkshare already specified"); YYERROR; } hfsc_opts.linkshare.m2 = $2; hfsc_opts.linkshare.used = 1; } | LINKSHARE '(' bandwidth comma NUMBER comma bandwidth ')' { if ($5 < 0 || $5 > INT_MAX) { yyerror("timing in curve out of range"); YYERROR; } if (hfsc_opts.linkshare.used) { yyerror("linkshare already specified"); YYERROR; } hfsc_opts.linkshare.m1 = $3; hfsc_opts.linkshare.d = $5; hfsc_opts.linkshare.m2 = $7; hfsc_opts.linkshare.used = 1; } | REALTIME bandwidth { if (hfsc_opts.realtime.used) { yyerror("realtime already specified"); YYERROR; } hfsc_opts.realtime.m2 = $2; hfsc_opts.realtime.used = 1; } | REALTIME '(' bandwidth comma NUMBER comma bandwidth ')' { if ($5 < 0 || $5 > INT_MAX) { yyerror("timing in curve out of range"); YYERROR; } if (hfsc_opts.realtime.used) { yyerror("realtime already specified"); YYERROR; } hfsc_opts.realtime.m1 = $3; hfsc_opts.realtime.d = $5; hfsc_opts.realtime.m2 = $7; hfsc_opts.realtime.used = 1; } | UPPERLIMIT bandwidth { if (hfsc_opts.upperlimit.used) { yyerror("upperlimit already specified"); YYERROR; } hfsc_opts.upperlimit.m2 = $2; hfsc_opts.upperlimit.used = 1; } | UPPERLIMIT '(' bandwidth comma NUMBER comma bandwidth ')' { if ($5 < 0 || $5 > INT_MAX) { yyerror("timing in curve out of range"); YYERROR; } if (hfsc_opts.upperlimit.used) { yyerror("upperlimit already specified"); YYERROR; } hfsc_opts.upperlimit.m1 = $3; hfsc_opts.upperlimit.d = $5; hfsc_opts.upperlimit.m2 = $7; hfsc_opts.upperlimit.used = 1; } | STRING { if (!strcmp($1, "default")) hfsc_opts.flags |= HFCF_DEFAULTCLASS; else if (!strcmp($1, "red")) hfsc_opts.flags |= HFCF_RED; else if (!strcmp($1, "ecn")) hfsc_opts.flags |= HFCF_RED|HFCF_ECN; else if (!strcmp($1, "rio")) hfsc_opts.flags |= HFCF_RIO; else if (!strcmp($1, "codel")) hfsc_opts.flags |= HFCF_CODEL; else { yyerror("unknown hfsc flag \"%s\"", $1); free($1); YYERROR; } free($1); } ; fairq_opts : { bzero(&fairq_opts, sizeof(struct node_fairq_opts)); } fairqopts_list { $$ = fairq_opts; } ; fairqopts_list : fairqopts_item | fairqopts_list comma fairqopts_item ; fairqopts_item : LINKSHARE bandwidth { if (fairq_opts.linkshare.used) { yyerror("linkshare already specified"); YYERROR; } fairq_opts.linkshare.m2 = $2; fairq_opts.linkshare.used = 1; } | LINKSHARE '(' bandwidth number bandwidth ')' { if (fairq_opts.linkshare.used) { yyerror("linkshare already specified"); YYERROR; } fairq_opts.linkshare.m1 = $3; fairq_opts.linkshare.d = $4; fairq_opts.linkshare.m2 = $5; fairq_opts.linkshare.used = 1; } | HOGS bandwidth { fairq_opts.hogs_bw = $2; } | BUCKETS number { fairq_opts.nbuckets = $2; } | STRING { if (!strcmp($1, "default")) fairq_opts.flags |= FARF_DEFAULTCLASS; else if (!strcmp($1, "red")) fairq_opts.flags |= FARF_RED; else if (!strcmp($1, "ecn")) fairq_opts.flags |= FARF_RED|FARF_ECN; else if (!strcmp($1, "rio")) fairq_opts.flags |= FARF_RIO; else if (!strcmp($1, "codel")) fairq_opts.flags |= FARF_CODEL; else { yyerror("unknown fairq flag \"%s\"", $1); free($1); YYERROR; } free($1); } ; codel_opts : { bzero(&codel_opts, sizeof(struct codel_opts)); } codelopts_list { $$ = codel_opts; } ; codelopts_list : codelopts_item | codelopts_list comma codelopts_item ; codelopts_item : INTERVAL number { if (codel_opts.interval) { yyerror("interval already specified"); YYERROR; } codel_opts.interval = $2; } | TARGET number { if (codel_opts.target) { yyerror("target already specified"); YYERROR; } codel_opts.target = $2; } | STRING { if (!strcmp($1, "ecn")) codel_opts.ecn = 1; else { yyerror("unknown codel option \"%s\"", $1); free($1); YYERROR; } free($1); } ; qassign : /* empty */ { $$ = NULL; } | qassign_item { $$ = $1; } | '{' optnl qassign_list '}' { $$ = $3; } ; qassign_list : qassign_item optnl { $$ = $1; } | qassign_list comma qassign_item optnl { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; qassign_item : STRING { $$ = calloc(1, sizeof(struct node_queue)); if ($$ == NULL) err(1, "qassign_item: calloc"); if (strlcpy($$->queue, $1, sizeof($$->queue)) >= sizeof($$->queue)) { yyerror("queue name '%s' too long (max " "%d chars)", $1, sizeof($$->queue)-1); free($1); free($$); YYERROR; } free($1); $$->next = NULL; $$->tail = $$; } ; pfrule : action dir logquick interface route af proto fromto filter_opts { - struct pf_rule r; + struct pfctl_rule r; struct node_state_opt *o; struct node_proto *proto; int srctrack = 0; int statelock = 0; int adaptive = 0; int defaults = 0; if (check_rulestate(PFCTL_STATE_FILTER)) YYERROR; memset(&r, 0, sizeof(r)); r.action = $1.b1; switch ($1.b2) { case PFRULE_RETURNRST: r.rule_flag |= PFRULE_RETURNRST; r.return_ttl = $1.w; break; case PFRULE_RETURNICMP: r.rule_flag |= PFRULE_RETURNICMP; r.return_icmp = $1.w; r.return_icmp6 = $1.w2; break; case PFRULE_RETURN: r.rule_flag |= PFRULE_RETURN; r.return_icmp = $1.w; r.return_icmp6 = $1.w2; break; } r.direction = $2; r.log = $3.log; r.logif = $3.logif; r.quick = $3.quick; r.prob = $9.prob; r.rtableid = $9.rtableid; if ($9.marker & FOM_PRIO) { if ($9.prio == 0) r.prio = PF_PRIO_ZERO; else r.prio = $9.prio; } if ($9.marker & FOM_SETPRIO) { r.set_prio[0] = $9.set_prio[0]; r.set_prio[1] = $9.set_prio[1]; r.scrub_flags |= PFSTATE_SETPRIO; } r.af = $6; if ($9.tag) if (strlcpy(r.tagname, $9.tag, PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } if ($9.match_tag) if (strlcpy(r.match_tagname, $9.match_tag, PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } r.match_tag_not = $9.match_tag_not; if (rule_label(&r, $9.label)) YYERROR; free($9.label); r.flags = $9.flags.b1; r.flagset = $9.flags.b2; if (($9.flags.b1 & $9.flags.b2) != $9.flags.b1) { yyerror("flags always false"); YYERROR; } if ($9.flags.b1 || $9.flags.b2 || $8.src_os) { for (proto = $7; proto != NULL && proto->proto != IPPROTO_TCP; proto = proto->next) ; /* nothing */ if (proto == NULL && $7 != NULL) { if ($9.flags.b1 || $9.flags.b2) yyerror( "flags only apply to tcp"); if ($8.src_os) yyerror( "OS fingerprinting only " "apply to tcp"); YYERROR; } #if 0 if (($9.flags.b1 & parse_flags("S")) == 0 && $8.src_os) { yyerror("OS fingerprinting requires " "the SYN TCP flag (flags S/SA)"); YYERROR; } #endif } r.tos = $9.tos; r.keep_state = $9.keep.action; o = $9.keep.options; /* 'keep state' by default on pass rules. */ if (!r.keep_state && !r.action && !($9.marker & FOM_KEEP)) { r.keep_state = PF_STATE_NORMAL; o = keep_state_defaults; defaults = 1; } while (o) { struct node_state_opt *p = o; switch (o->type) { case PF_STATE_OPT_MAX: if (r.max_states) { yyerror("state option 'max' " "multiple definitions"); YYERROR; } r.max_states = o->data.max_states; break; case PF_STATE_OPT_NOSYNC: if (r.rule_flag & PFRULE_NOSYNC) { yyerror("state option 'sync' " "multiple definitions"); YYERROR; } r.rule_flag |= PFRULE_NOSYNC; break; case PF_STATE_OPT_SRCTRACK: if (srctrack) { yyerror("state option " "'source-track' " "multiple definitions"); YYERROR; } srctrack = o->data.src_track; r.rule_flag |= PFRULE_SRCTRACK; break; case PF_STATE_OPT_MAX_SRC_STATES: if (r.max_src_states) { yyerror("state option " "'max-src-states' " "multiple definitions"); YYERROR; } if (o->data.max_src_states == 0) { yyerror("'max-src-states' must " "be > 0"); YYERROR; } r.max_src_states = o->data.max_src_states; r.rule_flag |= PFRULE_SRCTRACK; break; case PF_STATE_OPT_OVERLOAD: if (r.overload_tblname[0]) { yyerror("multiple 'overload' " "table definitions"); YYERROR; } if (strlcpy(r.overload_tblname, o->data.overload.tblname, PF_TABLE_NAME_SIZE) >= PF_TABLE_NAME_SIZE) { yyerror("state option: " "strlcpy"); YYERROR; } r.flush = o->data.overload.flush; break; case PF_STATE_OPT_MAX_SRC_CONN: if (r.max_src_conn) { yyerror("state option " "'max-src-conn' " "multiple definitions"); YYERROR; } if (o->data.max_src_conn == 0) { yyerror("'max-src-conn' " "must be > 0"); YYERROR; } r.max_src_conn = o->data.max_src_conn; r.rule_flag |= PFRULE_SRCTRACK | PFRULE_RULESRCTRACK; break; case PF_STATE_OPT_MAX_SRC_CONN_RATE: if (r.max_src_conn_rate.limit) { yyerror("state option " "'max-src-conn-rate' " "multiple definitions"); YYERROR; } if (!o->data.max_src_conn_rate.limit || !o->data.max_src_conn_rate.seconds) { yyerror("'max-src-conn-rate' " "values must be > 0"); YYERROR; } if (o->data.max_src_conn_rate.limit > PF_THRESHOLD_MAX) { yyerror("'max-src-conn-rate' " "maximum rate must be < %u", PF_THRESHOLD_MAX); YYERROR; } r.max_src_conn_rate.limit = o->data.max_src_conn_rate.limit; r.max_src_conn_rate.seconds = o->data.max_src_conn_rate.seconds; r.rule_flag |= PFRULE_SRCTRACK | PFRULE_RULESRCTRACK; break; case PF_STATE_OPT_MAX_SRC_NODES: if (r.max_src_nodes) { yyerror("state option " "'max-src-nodes' " "multiple definitions"); YYERROR; } if (o->data.max_src_nodes == 0) { yyerror("'max-src-nodes' must " "be > 0"); YYERROR; } r.max_src_nodes = o->data.max_src_nodes; r.rule_flag |= PFRULE_SRCTRACK | PFRULE_RULESRCTRACK; break; case PF_STATE_OPT_STATELOCK: if (statelock) { yyerror("state locking option: " "multiple definitions"); YYERROR; } statelock = 1; r.rule_flag |= o->data.statelock; break; case PF_STATE_OPT_SLOPPY: if (r.rule_flag & PFRULE_STATESLOPPY) { yyerror("state sloppy option: " "multiple definitions"); YYERROR; } r.rule_flag |= PFRULE_STATESLOPPY; break; case PF_STATE_OPT_TIMEOUT: if (o->data.timeout.number == PFTM_ADAPTIVE_START || o->data.timeout.number == PFTM_ADAPTIVE_END) adaptive = 1; if (r.timeout[o->data.timeout.number]) { yyerror("state timeout %s " "multiple definitions", pf_timeouts[o->data. timeout.number].name); YYERROR; } r.timeout[o->data.timeout.number] = o->data.timeout.seconds; } o = o->next; if (!defaults) free(p); } /* 'flags S/SA' by default on stateful rules */ if (!r.action && !r.flags && !r.flagset && !$9.fragment && !($9.marker & FOM_FLAGS) && r.keep_state) { r.flags = parse_flags("S"); r.flagset = parse_flags("SA"); } if (!adaptive && r.max_states) { r.timeout[PFTM_ADAPTIVE_START] = (r.max_states / 10) * 6; r.timeout[PFTM_ADAPTIVE_END] = (r.max_states / 10) * 12; } if (r.rule_flag & PFRULE_SRCTRACK) { if (srctrack == PF_SRCTRACK_GLOBAL && r.max_src_nodes) { yyerror("'max-src-nodes' is " "incompatible with " "'source-track global'"); YYERROR; } if (srctrack == PF_SRCTRACK_GLOBAL && r.max_src_conn) { yyerror("'max-src-conn' is " "incompatible with " "'source-track global'"); YYERROR; } if (srctrack == PF_SRCTRACK_GLOBAL && r.max_src_conn_rate.seconds) { yyerror("'max-src-conn-rate' is " "incompatible with " "'source-track global'"); YYERROR; } if (r.timeout[PFTM_SRC_NODE] < r.max_src_conn_rate.seconds) r.timeout[PFTM_SRC_NODE] = r.max_src_conn_rate.seconds; r.rule_flag |= PFRULE_SRCTRACK; if (srctrack == PF_SRCTRACK_RULE) r.rule_flag |= PFRULE_RULESRCTRACK; } if (r.keep_state && !statelock) r.rule_flag |= default_statelock; if ($9.fragment) r.rule_flag |= PFRULE_FRAGMENT; r.allow_opts = $9.allowopts; decide_address_family($8.src.host, &r.af); decide_address_family($8.dst.host, &r.af); if ($5.rt) { if (!r.direction) { yyerror("direction must be explicit " "with rules that specify routing"); YYERROR; } r.rt = $5.rt; r.rpool.opts = $5.pool_opts; if ($5.key != NULL) memcpy(&r.rpool.key, $5.key, sizeof(struct pf_poolhashkey)); } if (r.rt) { decide_address_family($5.host, &r.af); remove_invalid_hosts(&$5.host, &r.af); if ($5.host == NULL) { yyerror("no routing address with " "matching address family found."); YYERROR; } if ((r.rpool.opts & PF_POOL_TYPEMASK) == PF_POOL_NONE && ($5.host->next != NULL || $5.host->addr.type == PF_ADDR_TABLE || DYNIF_MULTIADDR($5.host->addr))) r.rpool.opts |= PF_POOL_ROUNDROBIN; if ((r.rpool.opts & PF_POOL_TYPEMASK) != PF_POOL_ROUNDROBIN && disallow_table($5.host, "tables are only " "supported in round-robin routing pools")) YYERROR; if ((r.rpool.opts & PF_POOL_TYPEMASK) != PF_POOL_ROUNDROBIN && disallow_alias($5.host, "interface (%s) " "is only supported in round-robin " "routing pools")) YYERROR; if ($5.host->next != NULL) { if ((r.rpool.opts & PF_POOL_TYPEMASK) != PF_POOL_ROUNDROBIN) { yyerror("r.rpool.opts must " "be PF_POOL_ROUNDROBIN"); YYERROR; } } } if ($9.queues.qname != NULL) { if (strlcpy(r.qname, $9.queues.qname, sizeof(r.qname)) >= sizeof(r.qname)) { yyerror("rule qname too long (max " "%d chars)", sizeof(r.qname)-1); YYERROR; } free($9.queues.qname); } if ($9.queues.pqname != NULL) { if (strlcpy(r.pqname, $9.queues.pqname, sizeof(r.pqname)) >= sizeof(r.pqname)) { yyerror("rule pqname too long (max " "%d chars)", sizeof(r.pqname)-1); YYERROR; } free($9.queues.pqname); } #ifdef __FreeBSD__ r.divert.port = $9.divert.port; #else if ((r.divert.port = $9.divert.port)) { if (r.direction == PF_OUT) { if ($9.divert.addr) { yyerror("address specified " "for outgoing divert"); YYERROR; } bzero(&r.divert.addr, sizeof(r.divert.addr)); } else { if (!$9.divert.addr) { yyerror("no address specified " "for incoming divert"); YYERROR; } if ($9.divert.addr->af != r.af) { yyerror("address family " "mismatch for divert"); YYERROR; } r.divert.addr = $9.divert.addr->addr.v.a.addr; } } #endif expand_rule(&r, $4, $5.host, $7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host, $8.dst.port, $9.uid, $9.gid, $9.icmpspec, ""); } ; filter_opts : { bzero(&filter_opts, sizeof filter_opts); filter_opts.rtableid = -1; } filter_opts_l { $$ = filter_opts; } | /* empty */ { bzero(&filter_opts, sizeof filter_opts); filter_opts.rtableid = -1; $$ = filter_opts; } ; filter_opts_l : filter_opts_l filter_opt | filter_opt ; filter_opt : USER uids { if (filter_opts.uid) $2->tail->next = filter_opts.uid; filter_opts.uid = $2; } | GROUP gids { if (filter_opts.gid) $2->tail->next = filter_opts.gid; filter_opts.gid = $2; } | flags { if (filter_opts.marker & FOM_FLAGS) { yyerror("flags cannot be redefined"); YYERROR; } filter_opts.marker |= FOM_FLAGS; filter_opts.flags.b1 |= $1.b1; filter_opts.flags.b2 |= $1.b2; filter_opts.flags.w |= $1.w; filter_opts.flags.w2 |= $1.w2; } | icmpspec { if (filter_opts.marker & FOM_ICMP) { yyerror("icmp-type cannot be redefined"); YYERROR; } filter_opts.marker |= FOM_ICMP; filter_opts.icmpspec = $1; } | PRIO NUMBER { if (filter_opts.marker & FOM_PRIO) { yyerror("prio cannot be redefined"); YYERROR; } if ($2 < 0 || $2 > PF_PRIO_MAX) { yyerror("prio must be 0 - %u", PF_PRIO_MAX); YYERROR; } filter_opts.marker |= FOM_PRIO; filter_opts.prio = $2; } | TOS tos { if (filter_opts.marker & FOM_TOS) { yyerror("tos cannot be redefined"); YYERROR; } filter_opts.marker |= FOM_TOS; filter_opts.tos = $2; } | keep { if (filter_opts.marker & FOM_KEEP) { yyerror("modulate or keep cannot be redefined"); YYERROR; } filter_opts.marker |= FOM_KEEP; filter_opts.keep.action = $1.action; filter_opts.keep.options = $1.options; } | FRAGMENT { filter_opts.fragment = 1; } | ALLOWOPTS { filter_opts.allowopts = 1; } | label { if (filter_opts.label) { yyerror("label cannot be redefined"); YYERROR; } filter_opts.label = $1; } | qname { if (filter_opts.queues.qname) { yyerror("queue cannot be redefined"); YYERROR; } filter_opts.queues = $1; } | TAG string { filter_opts.tag = $2; } | not TAGGED string { filter_opts.match_tag = $3; filter_opts.match_tag_not = $1; } | PROBABILITY probability { double p; p = floor($2 * UINT_MAX + 0.5); if (p < 0.0 || p > UINT_MAX) { yyerror("invalid probability: %lf", p); YYERROR; } filter_opts.prob = (u_int32_t)p; if (filter_opts.prob == 0) filter_opts.prob = 1; } | RTABLE NUMBER { if ($2 < 0 || $2 > rt_tableid_max()) { yyerror("invalid rtable id"); YYERROR; } filter_opts.rtableid = $2; } | DIVERTTO portplain { #ifdef __FreeBSD__ filter_opts.divert.port = $2.a; if (!filter_opts.divert.port) { yyerror("invalid divert port: %u", ntohs($2.a)); YYERROR; } #endif } | DIVERTTO STRING PORT portplain { #ifndef __FreeBSD__ if ((filter_opts.divert.addr = host($2)) == NULL) { yyerror("could not parse divert address: %s", $2); free($2); YYERROR; } #else if ($2) #endif free($2); filter_opts.divert.port = $4.a; if (!filter_opts.divert.port) { yyerror("invalid divert port: %u", ntohs($4.a)); YYERROR; } } | DIVERTREPLY { #ifdef __FreeBSD__ yyerror("divert-reply has no meaning in FreeBSD pf(4)"); YYERROR; #else filter_opts.divert.port = 1; /* some random value */ #endif } | filter_sets ; filter_sets : SET '(' filter_sets_l ')' { $$ = filter_opts; } | SET filter_set { $$ = filter_opts; } ; filter_sets_l : filter_sets_l comma filter_set | filter_set ; filter_set : prio { if (filter_opts.marker & FOM_SETPRIO) { yyerror("prio cannot be redefined"); YYERROR; } filter_opts.marker |= FOM_SETPRIO; filter_opts.set_prio[0] = $1.b1; filter_opts.set_prio[1] = $1.b2; } prio : PRIO NUMBER { if ($2 < 0 || $2 > PF_PRIO_MAX) { yyerror("prio must be 0 - %u", PF_PRIO_MAX); YYERROR; } $$.b1 = $$.b2 = $2; } | PRIO '(' NUMBER comma NUMBER ')' { if ($3 < 0 || $3 > PF_PRIO_MAX || $5 < 0 || $5 > PF_PRIO_MAX) { yyerror("prio must be 0 - %u", PF_PRIO_MAX); YYERROR; } $$.b1 = $3; $$.b2 = $5; } ; probability : STRING { char *e; double p = strtod($1, &e); if (*e == '%') { p *= 0.01; e++; } if (*e) { yyerror("invalid probability: %s", $1); free($1); YYERROR; } free($1); $$ = p; } | NUMBER { $$ = (double)$1; } ; action : PASS { $$.b1 = PF_PASS; $$.b2 = failpolicy; $$.w = returnicmpdefault; $$.w2 = returnicmp6default; } | BLOCK blockspec { $$ = $2; $$.b1 = PF_DROP; } ; blockspec : /* empty */ { $$.b2 = blockpolicy; $$.w = returnicmpdefault; $$.w2 = returnicmp6default; } | DROP { $$.b2 = PFRULE_DROP; $$.w = 0; $$.w2 = 0; } | RETURNRST { $$.b2 = PFRULE_RETURNRST; $$.w = 0; $$.w2 = 0; } | RETURNRST '(' TTL NUMBER ')' { if ($4 < 0 || $4 > 255) { yyerror("illegal ttl value %d", $4); YYERROR; } $$.b2 = PFRULE_RETURNRST; $$.w = $4; $$.w2 = 0; } | RETURNICMP { $$.b2 = PFRULE_RETURNICMP; $$.w = returnicmpdefault; $$.w2 = returnicmp6default; } | RETURNICMP6 { $$.b2 = PFRULE_RETURNICMP; $$.w = returnicmpdefault; $$.w2 = returnicmp6default; } | RETURNICMP '(' reticmpspec ')' { $$.b2 = PFRULE_RETURNICMP; $$.w = $3; $$.w2 = returnicmpdefault; } | RETURNICMP6 '(' reticmp6spec ')' { $$.b2 = PFRULE_RETURNICMP; $$.w = returnicmpdefault; $$.w2 = $3; } | RETURNICMP '(' reticmpspec comma reticmp6spec ')' { $$.b2 = PFRULE_RETURNICMP; $$.w = $3; $$.w2 = $5; } | RETURN { $$.b2 = PFRULE_RETURN; $$.w = returnicmpdefault; $$.w2 = returnicmp6default; } ; reticmpspec : STRING { if (!($$ = parseicmpspec($1, AF_INET))) { free($1); YYERROR; } free($1); } | NUMBER { u_int8_t icmptype; if ($1 < 0 || $1 > 255) { yyerror("invalid icmp code %lu", $1); YYERROR; } icmptype = returnicmpdefault >> 8; $$ = (icmptype << 8 | $1); } ; reticmp6spec : STRING { if (!($$ = parseicmpspec($1, AF_INET6))) { free($1); YYERROR; } free($1); } | NUMBER { u_int8_t icmptype; if ($1 < 0 || $1 > 255) { yyerror("invalid icmp code %lu", $1); YYERROR; } icmptype = returnicmp6default >> 8; $$ = (icmptype << 8 | $1); } ; dir : /* empty */ { $$ = PF_INOUT; } | IN { $$ = PF_IN; } | OUT { $$ = PF_OUT; } ; quick : /* empty */ { $$.quick = 0; } | QUICK { $$.quick = 1; } ; logquick : /* empty */ { $$.log = 0; $$.quick = 0; $$.logif = 0; } | log { $$ = $1; $$.quick = 0; } | QUICK { $$.quick = 1; $$.log = 0; $$.logif = 0; } | log QUICK { $$ = $1; $$.quick = 1; } | QUICK log { $$ = $2; $$.quick = 1; } ; log : LOG { $$.log = PF_LOG; $$.logif = 0; } | LOG '(' logopts ')' { $$.log = PF_LOG | $3.log; $$.logif = $3.logif; } ; logopts : logopt { $$ = $1; } | logopts comma logopt { $$.log = $1.log | $3.log; $$.logif = $3.logif; if ($$.logif == 0) $$.logif = $1.logif; } ; logopt : ALL { $$.log = PF_LOG_ALL; $$.logif = 0; } | USER { $$.log = PF_LOG_SOCKET_LOOKUP; $$.logif = 0; } | GROUP { $$.log = PF_LOG_SOCKET_LOOKUP; $$.logif = 0; } | TO string { const char *errstr; u_int i; $$.log = 0; if (strncmp($2, "pflog", 5)) { yyerror("%s: should be a pflog interface", $2); free($2); YYERROR; } i = strtonum($2 + 5, 0, 255, &errstr); if (errstr) { yyerror("%s: %s", $2, errstr); free($2); YYERROR; } free($2); $$.logif = i; } ; interface : /* empty */ { $$ = NULL; } | ON if_item_not { $$ = $2; } | ON '{' optnl if_list '}' { $$ = $4; } ; if_list : if_item_not optnl { $$ = $1; } | if_list comma if_item_not optnl { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; if_item_not : not if_item { $$ = $2; $$->not = $1; } ; if_item : STRING { struct node_host *n; $$ = calloc(1, sizeof(struct node_if)); if ($$ == NULL) err(1, "if_item: calloc"); if (strlcpy($$->ifname, $1, sizeof($$->ifname)) >= sizeof($$->ifname)) { free($1); free($$); yyerror("interface name too long"); YYERROR; } if ((n = ifa_exists($1)) != NULL) $$->ifa_flags = n->ifa_flags; free($1); $$->not = 0; $$->next = NULL; $$->tail = $$; } ; af : /* empty */ { $$ = 0; } | INET { $$ = AF_INET; } | INET6 { $$ = AF_INET6; } ; proto : /* empty */ { $$ = NULL; } | PROTO proto_item { $$ = $2; } | PROTO '{' optnl proto_list '}' { $$ = $4; } ; proto_list : proto_item optnl { $$ = $1; } | proto_list comma proto_item optnl { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; proto_item : protoval { u_int8_t pr; pr = (u_int8_t)$1; if (pr == 0) { yyerror("proto 0 cannot be used"); YYERROR; } $$ = calloc(1, sizeof(struct node_proto)); if ($$ == NULL) err(1, "proto_item: calloc"); $$->proto = pr; $$->next = NULL; $$->tail = $$; } ; protoval : STRING { struct protoent *p; p = getprotobyname($1); if (p == NULL) { yyerror("unknown protocol %s", $1); free($1); YYERROR; } $$ = p->p_proto; free($1); } | NUMBER { if ($1 < 0 || $1 > 255) { yyerror("protocol outside range"); YYERROR; } } ; fromto : ALL { $$.src.host = NULL; $$.src.port = NULL; $$.dst.host = NULL; $$.dst.port = NULL; $$.src_os = NULL; } | from os to { $$.src = $1; $$.src_os = $2; $$.dst = $3; } ; os : /* empty */ { $$ = NULL; } | OS xos { $$ = $2; } | OS '{' optnl os_list '}' { $$ = $4; } ; xos : STRING { $$ = calloc(1, sizeof(struct node_os)); if ($$ == NULL) err(1, "os: calloc"); $$->os = $1; $$->tail = $$; } ; os_list : xos optnl { $$ = $1; } | os_list comma xos optnl { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; from : /* empty */ { $$.host = NULL; $$.port = NULL; } | FROM ipportspec { $$ = $2; } ; to : /* empty */ { $$.host = NULL; $$.port = NULL; } | TO ipportspec { if (disallow_urpf_failed($2.host, "\"urpf-failed\" is " "not permitted in a destination address")) YYERROR; $$ = $2; } ; ipportspec : ipspec { $$.host = $1; $$.port = NULL; } | ipspec PORT portspec { $$.host = $1; $$.port = $3; } | PORT portspec { $$.host = NULL; $$.port = $2; } ; optnl : '\n' optnl | ; ipspec : ANY { $$ = NULL; } | xhost { $$ = $1; } | '{' optnl host_list '}' { $$ = $3; } ; toipspec : TO ipspec { $$ = $2; } | /* empty */ { $$ = NULL; } ; host_list : ipspec optnl { $$ = $1; } | host_list comma ipspec optnl { if ($3 == NULL) $$ = $1; else if ($1 == NULL) $$ = $3; else { $1->tail->next = $3; $1->tail = $3->tail; $$ = $1; } } ; xhost : not host { struct node_host *n; for (n = $2; n != NULL; n = n->next) n->not = $1; $$ = $2; } | not NOROUTE { $$ = calloc(1, sizeof(struct node_host)); if ($$ == NULL) err(1, "xhost: calloc"); $$->addr.type = PF_ADDR_NOROUTE; $$->next = NULL; $$->not = $1; $$->tail = $$; } | not URPFFAILED { $$ = calloc(1, sizeof(struct node_host)); if ($$ == NULL) err(1, "xhost: calloc"); $$->addr.type = PF_ADDR_URPFFAILED; $$->next = NULL; $$->not = $1; $$->tail = $$; } ; host : STRING { if (($$ = host($1)) == NULL) { /* error. "any" is handled elsewhere */ free($1); yyerror("could not parse host specification"); YYERROR; } free($1); } | STRING '-' STRING { struct node_host *b, *e; if ((b = host($1)) == NULL || (e = host($3)) == NULL) { free($1); free($3); yyerror("could not parse host specification"); YYERROR; } if (b->af != e->af || b->addr.type != PF_ADDR_ADDRMASK || e->addr.type != PF_ADDR_ADDRMASK || unmask(&b->addr.v.a.mask, b->af) != (b->af == AF_INET ? 32 : 128) || unmask(&e->addr.v.a.mask, e->af) != (e->af == AF_INET ? 32 : 128) || b->next != NULL || b->not || e->next != NULL || e->not) { free(b); free(e); free($1); free($3); yyerror("invalid address range"); YYERROR; } memcpy(&b->addr.v.a.mask, &e->addr.v.a.addr, sizeof(b->addr.v.a.mask)); b->addr.type = PF_ADDR_RANGE; $$ = b; free(e); free($1); free($3); } | STRING '/' NUMBER { char *buf; if (asprintf(&buf, "%s/%lld", $1, (long long)$3) == -1) err(1, "host: asprintf"); free($1); if (($$ = host(buf)) == NULL) { /* error. "any" is handled elsewhere */ free(buf); yyerror("could not parse host specification"); YYERROR; } free(buf); } | NUMBER '/' NUMBER { char *buf; /* ie. for 10/8 parsing */ #ifdef __FreeBSD__ if (asprintf(&buf, "%lld/%lld", (long long)$1, (long long)$3) == -1) #else if (asprintf(&buf, "%lld/%lld", $1, $3) == -1) #endif err(1, "host: asprintf"); if (($$ = host(buf)) == NULL) { /* error. "any" is handled elsewhere */ free(buf); yyerror("could not parse host specification"); YYERROR; } free(buf); } | dynaddr | dynaddr '/' NUMBER { struct node_host *n; if ($3 < 0 || $3 > 128) { yyerror("bit number too big"); YYERROR; } $$ = $1; for (n = $1; n != NULL; n = n->next) set_ipmask(n, $3); } | '<' STRING '>' { if (strlen($2) >= PF_TABLE_NAME_SIZE) { yyerror("table name '%s' too long", $2); free($2); YYERROR; } $$ = calloc(1, sizeof(struct node_host)); if ($$ == NULL) err(1, "host: calloc"); $$->addr.type = PF_ADDR_TABLE; if (strlcpy($$->addr.v.tblname, $2, sizeof($$->addr.v.tblname)) >= sizeof($$->addr.v.tblname)) errx(1, "host: strlcpy"); free($2); $$->next = NULL; $$->tail = $$; } ; number : NUMBER | STRING { u_long ulval; if (atoul($1, &ulval) == -1) { yyerror("%s is not a number", $1); free($1); YYERROR; } else $$ = ulval; free($1); } ; dynaddr : '(' STRING ')' { int flags = 0; char *p, *op; op = $2; if (!isalpha(op[0])) { yyerror("invalid interface name '%s'", op); free(op); YYERROR; } while ((p = strrchr($2, ':')) != NULL) { if (!strcmp(p+1, "network")) flags |= PFI_AFLAG_NETWORK; else if (!strcmp(p+1, "broadcast")) flags |= PFI_AFLAG_BROADCAST; else if (!strcmp(p+1, "peer")) flags |= PFI_AFLAG_PEER; else if (!strcmp(p+1, "0")) flags |= PFI_AFLAG_NOALIAS; else { yyerror("interface %s has bad modifier", $2); free(op); YYERROR; } *p = '\0'; } if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) { free(op); yyerror("illegal combination of " "interface modifiers"); YYERROR; } $$ = calloc(1, sizeof(struct node_host)); if ($$ == NULL) err(1, "address: calloc"); $$->af = 0; set_ipmask($$, 128); $$->addr.type = PF_ADDR_DYNIFTL; $$->addr.iflags = flags; if (strlcpy($$->addr.v.ifname, $2, sizeof($$->addr.v.ifname)) >= sizeof($$->addr.v.ifname)) { free(op); free($$); yyerror("interface name too long"); YYERROR; } free(op); $$->next = NULL; $$->tail = $$; } ; portspec : port_item { $$ = $1; } | '{' optnl port_list '}' { $$ = $3; } ; port_list : port_item optnl { $$ = $1; } | port_list comma port_item optnl { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; port_item : portrange { $$ = calloc(1, sizeof(struct node_port)); if ($$ == NULL) err(1, "port_item: calloc"); $$->port[0] = $1.a; $$->port[1] = $1.b; if ($1.t) $$->op = PF_OP_RRG; else $$->op = PF_OP_EQ; $$->next = NULL; $$->tail = $$; } | unaryop portrange { if ($2.t) { yyerror("':' cannot be used with an other " "port operator"); YYERROR; } $$ = calloc(1, sizeof(struct node_port)); if ($$ == NULL) err(1, "port_item: calloc"); $$->port[0] = $2.a; $$->port[1] = $2.b; $$->op = $1; $$->next = NULL; $$->tail = $$; } | portrange PORTBINARY portrange { if ($1.t || $3.t) { yyerror("':' cannot be used with an other " "port operator"); YYERROR; } $$ = calloc(1, sizeof(struct node_port)); if ($$ == NULL) err(1, "port_item: calloc"); $$->port[0] = $1.a; $$->port[1] = $3.a; $$->op = $2; $$->next = NULL; $$->tail = $$; } ; portplain : numberstring { if (parseport($1, &$$, 0) == -1) { free($1); YYERROR; } free($1); } ; portrange : numberstring { if (parseport($1, &$$, PPORT_RANGE) == -1) { free($1); YYERROR; } free($1); } ; uids : uid_item { $$ = $1; } | '{' optnl uid_list '}' { $$ = $3; } ; uid_list : uid_item optnl { $$ = $1; } | uid_list comma uid_item optnl { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; uid_item : uid { $$ = calloc(1, sizeof(struct node_uid)); if ($$ == NULL) err(1, "uid_item: calloc"); $$->uid[0] = $1; $$->uid[1] = $1; $$->op = PF_OP_EQ; $$->next = NULL; $$->tail = $$; } | unaryop uid { if ($2 == UID_MAX && $1 != PF_OP_EQ && $1 != PF_OP_NE) { yyerror("user unknown requires operator = or " "!="); YYERROR; } $$ = calloc(1, sizeof(struct node_uid)); if ($$ == NULL) err(1, "uid_item: calloc"); $$->uid[0] = $2; $$->uid[1] = $2; $$->op = $1; $$->next = NULL; $$->tail = $$; } | uid PORTBINARY uid { if ($1 == UID_MAX || $3 == UID_MAX) { yyerror("user unknown requires operator = or " "!="); YYERROR; } $$ = calloc(1, sizeof(struct node_uid)); if ($$ == NULL) err(1, "uid_item: calloc"); $$->uid[0] = $1; $$->uid[1] = $3; $$->op = $2; $$->next = NULL; $$->tail = $$; } ; uid : STRING { if (!strcmp($1, "unknown")) $$ = UID_MAX; else { struct passwd *pw; if ((pw = getpwnam($1)) == NULL) { yyerror("unknown user %s", $1); free($1); YYERROR; } $$ = pw->pw_uid; } free($1); } | NUMBER { if ($1 < 0 || $1 >= UID_MAX) { yyerror("illegal uid value %lu", $1); YYERROR; } $$ = $1; } ; gids : gid_item { $$ = $1; } | '{' optnl gid_list '}' { $$ = $3; } ; gid_list : gid_item optnl { $$ = $1; } | gid_list comma gid_item optnl { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; gid_item : gid { $$ = calloc(1, sizeof(struct node_gid)); if ($$ == NULL) err(1, "gid_item: calloc"); $$->gid[0] = $1; $$->gid[1] = $1; $$->op = PF_OP_EQ; $$->next = NULL; $$->tail = $$; } | unaryop gid { if ($2 == GID_MAX && $1 != PF_OP_EQ && $1 != PF_OP_NE) { yyerror("group unknown requires operator = or " "!="); YYERROR; } $$ = calloc(1, sizeof(struct node_gid)); if ($$ == NULL) err(1, "gid_item: calloc"); $$->gid[0] = $2; $$->gid[1] = $2; $$->op = $1; $$->next = NULL; $$->tail = $$; } | gid PORTBINARY gid { if ($1 == GID_MAX || $3 == GID_MAX) { yyerror("group unknown requires operator = or " "!="); YYERROR; } $$ = calloc(1, sizeof(struct node_gid)); if ($$ == NULL) err(1, "gid_item: calloc"); $$->gid[0] = $1; $$->gid[1] = $3; $$->op = $2; $$->next = NULL; $$->tail = $$; } ; gid : STRING { if (!strcmp($1, "unknown")) $$ = GID_MAX; else { struct group *grp; if ((grp = getgrnam($1)) == NULL) { yyerror("unknown group %s", $1); free($1); YYERROR; } $$ = grp->gr_gid; } free($1); } | NUMBER { if ($1 < 0 || $1 >= GID_MAX) { yyerror("illegal gid value %lu", $1); YYERROR; } $$ = $1; } ; flag : STRING { int f; if ((f = parse_flags($1)) < 0) { yyerror("bad flags %s", $1); free($1); YYERROR; } free($1); $$.b1 = f; } ; flags : FLAGS flag '/' flag { $$.b1 = $2.b1; $$.b2 = $4.b1; } | FLAGS '/' flag { $$.b1 = 0; $$.b2 = $3.b1; } | FLAGS ANY { $$.b1 = 0; $$.b2 = 0; } ; icmpspec : ICMPTYPE icmp_item { $$ = $2; } | ICMPTYPE '{' optnl icmp_list '}' { $$ = $4; } | ICMP6TYPE icmp6_item { $$ = $2; } | ICMP6TYPE '{' optnl icmp6_list '}' { $$ = $4; } ; icmp_list : icmp_item optnl { $$ = $1; } | icmp_list comma icmp_item optnl { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; icmp6_list : icmp6_item optnl { $$ = $1; } | icmp6_list comma icmp6_item optnl { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; icmp_item : icmptype { $$ = calloc(1, sizeof(struct node_icmp)); if ($$ == NULL) err(1, "icmp_item: calloc"); $$->type = $1; $$->code = 0; $$->proto = IPPROTO_ICMP; $$->next = NULL; $$->tail = $$; } | icmptype CODE STRING { const struct icmpcodeent *p; if ((p = geticmpcodebyname($1-1, $3, AF_INET)) == NULL) { yyerror("unknown icmp-code %s", $3); free($3); YYERROR; } free($3); $$ = calloc(1, sizeof(struct node_icmp)); if ($$ == NULL) err(1, "icmp_item: calloc"); $$->type = $1; $$->code = p->code + 1; $$->proto = IPPROTO_ICMP; $$->next = NULL; $$->tail = $$; } | icmptype CODE NUMBER { if ($3 < 0 || $3 > 255) { yyerror("illegal icmp-code %lu", $3); YYERROR; } $$ = calloc(1, sizeof(struct node_icmp)); if ($$ == NULL) err(1, "icmp_item: calloc"); $$->type = $1; $$->code = $3 + 1; $$->proto = IPPROTO_ICMP; $$->next = NULL; $$->tail = $$; } ; icmp6_item : icmp6type { $$ = calloc(1, sizeof(struct node_icmp)); if ($$ == NULL) err(1, "icmp_item: calloc"); $$->type = $1; $$->code = 0; $$->proto = IPPROTO_ICMPV6; $$->next = NULL; $$->tail = $$; } | icmp6type CODE STRING { const struct icmpcodeent *p; if ((p = geticmpcodebyname($1-1, $3, AF_INET6)) == NULL) { yyerror("unknown icmp6-code %s", $3); free($3); YYERROR; } free($3); $$ = calloc(1, sizeof(struct node_icmp)); if ($$ == NULL) err(1, "icmp_item: calloc"); $$->type = $1; $$->code = p->code + 1; $$->proto = IPPROTO_ICMPV6; $$->next = NULL; $$->tail = $$; } | icmp6type CODE NUMBER { if ($3 < 0 || $3 > 255) { yyerror("illegal icmp-code %lu", $3); YYERROR; } $$ = calloc(1, sizeof(struct node_icmp)); if ($$ == NULL) err(1, "icmp_item: calloc"); $$->type = $1; $$->code = $3 + 1; $$->proto = IPPROTO_ICMPV6; $$->next = NULL; $$->tail = $$; } ; icmptype : STRING { const struct icmptypeent *p; if ((p = geticmptypebyname($1, AF_INET)) == NULL) { yyerror("unknown icmp-type %s", $1); free($1); YYERROR; } $$ = p->type + 1; free($1); } | NUMBER { if ($1 < 0 || $1 > 255) { yyerror("illegal icmp-type %lu", $1); YYERROR; } $$ = $1 + 1; } ; icmp6type : STRING { const struct icmptypeent *p; if ((p = geticmptypebyname($1, AF_INET6)) == NULL) { yyerror("unknown icmp6-type %s", $1); free($1); YYERROR; } $$ = p->type + 1; free($1); } | NUMBER { if ($1 < 0 || $1 > 255) { yyerror("illegal icmp6-type %lu", $1); YYERROR; } $$ = $1 + 1; } ; tos : STRING { int val; char *end; if (map_tos($1, &val)) $$ = val; else if ($1[0] == '0' && $1[1] == 'x') { errno = 0; $$ = strtoul($1, &end, 16); if (errno || *end != '\0') $$ = 256; } else $$ = 256; /* flag bad argument */ if ($$ < 0 || $$ > 255) { yyerror("illegal tos value %s", $1); free($1); YYERROR; } free($1); } | NUMBER { $$ = $1; if ($$ < 0 || $$ > 255) { yyerror("illegal tos value %s", $1); YYERROR; } } ; sourcetrack : SOURCETRACK { $$ = PF_SRCTRACK; } | SOURCETRACK GLOBAL { $$ = PF_SRCTRACK_GLOBAL; } | SOURCETRACK RULE { $$ = PF_SRCTRACK_RULE; } ; statelock : IFBOUND { $$ = PFRULE_IFBOUND; } | FLOATING { $$ = 0; } ; keep : NO STATE { $$.action = 0; $$.options = NULL; } | KEEP STATE state_opt_spec { $$.action = PF_STATE_NORMAL; $$.options = $3; } | MODULATE STATE state_opt_spec { $$.action = PF_STATE_MODULATE; $$.options = $3; } | SYNPROXY STATE state_opt_spec { $$.action = PF_STATE_SYNPROXY; $$.options = $3; } ; flush : /* empty */ { $$ = 0; } | FLUSH { $$ = PF_FLUSH; } | FLUSH GLOBAL { $$ = PF_FLUSH | PF_FLUSH_GLOBAL; } ; state_opt_spec : '(' state_opt_list ')' { $$ = $2; } | /* empty */ { $$ = NULL; } ; state_opt_list : state_opt_item { $$ = $1; } | state_opt_list comma state_opt_item { $1->tail->next = $3; $1->tail = $3; $$ = $1; } ; state_opt_item : MAXIMUM NUMBER { if ($2 < 0 || $2 > UINT_MAX) { yyerror("only positive values permitted"); YYERROR; } $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_MAX; $$->data.max_states = $2; $$->next = NULL; $$->tail = $$; } | NOSYNC { $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_NOSYNC; $$->next = NULL; $$->tail = $$; } | MAXSRCSTATES NUMBER { if ($2 < 0 || $2 > UINT_MAX) { yyerror("only positive values permitted"); YYERROR; } $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_MAX_SRC_STATES; $$->data.max_src_states = $2; $$->next = NULL; $$->tail = $$; } | MAXSRCCONN NUMBER { if ($2 < 0 || $2 > UINT_MAX) { yyerror("only positive values permitted"); YYERROR; } $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_MAX_SRC_CONN; $$->data.max_src_conn = $2; $$->next = NULL; $$->tail = $$; } | MAXSRCCONNRATE NUMBER '/' NUMBER { if ($2 < 0 || $2 > UINT_MAX || $4 < 0 || $4 > UINT_MAX) { yyerror("only positive values permitted"); YYERROR; } $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_MAX_SRC_CONN_RATE; $$->data.max_src_conn_rate.limit = $2; $$->data.max_src_conn_rate.seconds = $4; $$->next = NULL; $$->tail = $$; } | OVERLOAD '<' STRING '>' flush { if (strlen($3) >= PF_TABLE_NAME_SIZE) { yyerror("table name '%s' too long", $3); free($3); YYERROR; } $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); if (strlcpy($$->data.overload.tblname, $3, PF_TABLE_NAME_SIZE) >= PF_TABLE_NAME_SIZE) errx(1, "state_opt_item: strlcpy"); free($3); $$->type = PF_STATE_OPT_OVERLOAD; $$->data.overload.flush = $5; $$->next = NULL; $$->tail = $$; } | MAXSRCNODES NUMBER { if ($2 < 0 || $2 > UINT_MAX) { yyerror("only positive values permitted"); YYERROR; } $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_MAX_SRC_NODES; $$->data.max_src_nodes = $2; $$->next = NULL; $$->tail = $$; } | sourcetrack { $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_SRCTRACK; $$->data.src_track = $1; $$->next = NULL; $$->tail = $$; } | statelock { $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_STATELOCK; $$->data.statelock = $1; $$->next = NULL; $$->tail = $$; } | SLOPPY { $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_SLOPPY; $$->next = NULL; $$->tail = $$; } | STRING NUMBER { int i; if ($2 < 0 || $2 > UINT_MAX) { yyerror("only positive values permitted"); YYERROR; } for (i = 0; pf_timeouts[i].name && strcmp(pf_timeouts[i].name, $1); ++i) ; /* nothing */ if (!pf_timeouts[i].name) { yyerror("illegal timeout name %s", $1); free($1); YYERROR; } if (strchr(pf_timeouts[i].name, '.') == NULL) { yyerror("illegal state timeout %s", $1); free($1); YYERROR; } free($1); $$ = calloc(1, sizeof(struct node_state_opt)); if ($$ == NULL) err(1, "state_opt_item: calloc"); $$->type = PF_STATE_OPT_TIMEOUT; $$->data.timeout.number = pf_timeouts[i].timeout; $$->data.timeout.seconds = $2; $$->next = NULL; $$->tail = $$; } ; label : LABEL STRING { $$ = $2; } ; qname : QUEUE STRING { $$.qname = $2; $$.pqname = NULL; } | QUEUE '(' STRING ')' { $$.qname = $3; $$.pqname = NULL; } | QUEUE '(' STRING comma STRING ')' { $$.qname = $3; $$.pqname = $5; } ; no : /* empty */ { $$ = 0; } | NO { $$ = 1; } ; portstar : numberstring { if (parseport($1, &$$, PPORT_RANGE|PPORT_STAR) == -1) { free($1); YYERROR; } free($1); } ; redirspec : host { $$ = $1; } | '{' optnl redir_host_list '}' { $$ = $3; } ; redir_host_list : host optnl { $$ = $1; } | redir_host_list comma host optnl { $1->tail->next = $3; $1->tail = $3->tail; $$ = $1; } ; redirpool : /* empty */ { $$ = NULL; } | ARROW redirspec { $$ = calloc(1, sizeof(struct redirection)); if ($$ == NULL) err(1, "redirection: calloc"); $$->host = $2; $$->rport.a = $$->rport.b = $$->rport.t = 0; } | ARROW redirspec PORT portstar { $$ = calloc(1, sizeof(struct redirection)); if ($$ == NULL) err(1, "redirection: calloc"); $$->host = $2; $$->rport = $4; } ; hashkey : /* empty */ { $$ = calloc(1, sizeof(struct pf_poolhashkey)); if ($$ == NULL) err(1, "hashkey: calloc"); $$->key32[0] = arc4random(); $$->key32[1] = arc4random(); $$->key32[2] = arc4random(); $$->key32[3] = arc4random(); } | string { if (!strncmp($1, "0x", 2)) { if (strlen($1) != 34) { free($1); yyerror("hex key must be 128 bits " "(32 hex digits) long"); YYERROR; } $$ = calloc(1, sizeof(struct pf_poolhashkey)); if ($$ == NULL) err(1, "hashkey: calloc"); if (sscanf($1, "0x%8x%8x%8x%8x", &$$->key32[0], &$$->key32[1], &$$->key32[2], &$$->key32[3]) != 4) { free($$); free($1); yyerror("invalid hex key"); YYERROR; } } else { MD5_CTX context; $$ = calloc(1, sizeof(struct pf_poolhashkey)); if ($$ == NULL) err(1, "hashkey: calloc"); MD5Init(&context); MD5Update(&context, (unsigned char *)$1, strlen($1)); MD5Final((unsigned char *)$$, &context); HTONL($$->key32[0]); HTONL($$->key32[1]); HTONL($$->key32[2]); HTONL($$->key32[3]); } free($1); } ; pool_opts : { bzero(&pool_opts, sizeof pool_opts); } pool_opts_l { $$ = pool_opts; } | /* empty */ { bzero(&pool_opts, sizeof pool_opts); $$ = pool_opts; } ; pool_opts_l : pool_opts_l pool_opt | pool_opt ; pool_opt : BITMASK { if (pool_opts.type) { yyerror("pool type cannot be redefined"); YYERROR; } pool_opts.type = PF_POOL_BITMASK; } | RANDOM { if (pool_opts.type) { yyerror("pool type cannot be redefined"); YYERROR; } pool_opts.type = PF_POOL_RANDOM; } | SOURCEHASH hashkey { if (pool_opts.type) { yyerror("pool type cannot be redefined"); YYERROR; } pool_opts.type = PF_POOL_SRCHASH; pool_opts.key = $2; } | ROUNDROBIN { if (pool_opts.type) { yyerror("pool type cannot be redefined"); YYERROR; } pool_opts.type = PF_POOL_ROUNDROBIN; } | STATICPORT { if (pool_opts.staticport) { yyerror("static-port cannot be redefined"); YYERROR; } pool_opts.staticport = 1; } | STICKYADDRESS { if (filter_opts.marker & POM_STICKYADDRESS) { yyerror("sticky-address cannot be redefined"); YYERROR; } pool_opts.marker |= POM_STICKYADDRESS; pool_opts.opts |= PF_POOL_STICKYADDR; } ; redirection : /* empty */ { $$ = NULL; } | ARROW host { $$ = calloc(1, sizeof(struct redirection)); if ($$ == NULL) err(1, "redirection: calloc"); $$->host = $2; $$->rport.a = $$->rport.b = $$->rport.t = 0; } | ARROW host PORT portstar { $$ = calloc(1, sizeof(struct redirection)); if ($$ == NULL) err(1, "redirection: calloc"); $$->host = $2; $$->rport = $4; } ; natpasslog : /* empty */ { $$.b1 = $$.b2 = 0; $$.w2 = 0; } | PASS { $$.b1 = 1; $$.b2 = 0; $$.w2 = 0; } | PASS log { $$.b1 = 1; $$.b2 = $2.log; $$.w2 = $2.logif; } | log { $$.b1 = 0; $$.b2 = $1.log; $$.w2 = $1.logif; } ; nataction : no NAT natpasslog { if ($1 && $3.b1) { yyerror("\"pass\" not valid with \"no\""); YYERROR; } if ($1) $$.b1 = PF_NONAT; else $$.b1 = PF_NAT; $$.b2 = $3.b1; $$.w = $3.b2; $$.w2 = $3.w2; } | no RDR natpasslog { if ($1 && $3.b1) { yyerror("\"pass\" not valid with \"no\""); YYERROR; } if ($1) $$.b1 = PF_NORDR; else $$.b1 = PF_RDR; $$.b2 = $3.b1; $$.w = $3.b2; $$.w2 = $3.w2; } ; natrule : nataction interface af proto fromto tag tagged rtable redirpool pool_opts { - struct pf_rule r; + struct pfctl_rule r; if (check_rulestate(PFCTL_STATE_NAT)) YYERROR; memset(&r, 0, sizeof(r)); r.action = $1.b1; r.natpass = $1.b2; r.log = $1.w; r.logif = $1.w2; r.af = $3; if (!r.af) { if ($5.src.host && $5.src.host->af && !$5.src.host->ifindex) r.af = $5.src.host->af; else if ($5.dst.host && $5.dst.host->af && !$5.dst.host->ifindex) r.af = $5.dst.host->af; } if ($6 != NULL) if (strlcpy(r.tagname, $6, PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } if ($7.name) if (strlcpy(r.match_tagname, $7.name, PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } r.match_tag_not = $7.neg; r.rtableid = $8; if (r.action == PF_NONAT || r.action == PF_NORDR) { if ($9 != NULL) { yyerror("translation rule with 'no' " "does not need '->'"); YYERROR; } } else { if ($9 == NULL || $9->host == NULL) { yyerror("translation rule requires '-> " "address'"); YYERROR; } if (!r.af && ! $9->host->ifindex) r.af = $9->host->af; remove_invalid_hosts(&$9->host, &r.af); if (invalid_redirect($9->host, r.af)) YYERROR; if (check_netmask($9->host, r.af)) YYERROR; r.rpool.proxy_port[0] = ntohs($9->rport.a); switch (r.action) { case PF_RDR: if (!$9->rport.b && $9->rport.t && $5.dst.port != NULL) { r.rpool.proxy_port[1] = ntohs($9->rport.a) + (ntohs( $5.dst.port->port[1]) - ntohs( $5.dst.port->port[0])); } else r.rpool.proxy_port[1] = ntohs($9->rport.b); break; case PF_NAT: r.rpool.proxy_port[1] = ntohs($9->rport.b); if (!r.rpool.proxy_port[0] && !r.rpool.proxy_port[1]) { r.rpool.proxy_port[0] = PF_NAT_PROXY_PORT_LOW; r.rpool.proxy_port[1] = PF_NAT_PROXY_PORT_HIGH; } else if (!r.rpool.proxy_port[1]) r.rpool.proxy_port[1] = r.rpool.proxy_port[0]; break; default: break; } r.rpool.opts = $10.type; if ((r.rpool.opts & PF_POOL_TYPEMASK) == PF_POOL_NONE && ($9->host->next != NULL || $9->host->addr.type == PF_ADDR_TABLE || DYNIF_MULTIADDR($9->host->addr))) r.rpool.opts = PF_POOL_ROUNDROBIN; if ((r.rpool.opts & PF_POOL_TYPEMASK) != PF_POOL_ROUNDROBIN && disallow_table($9->host, "tables are only " "supported in round-robin redirection " "pools")) YYERROR; if ((r.rpool.opts & PF_POOL_TYPEMASK) != PF_POOL_ROUNDROBIN && disallow_alias($9->host, "interface (%s) " "is only supported in round-robin " "redirection pools")) YYERROR; if ($9->host->next != NULL) { if ((r.rpool.opts & PF_POOL_TYPEMASK) != PF_POOL_ROUNDROBIN) { yyerror("only round-robin " "valid for multiple " "redirection addresses"); YYERROR; } } } if ($10.key != NULL) memcpy(&r.rpool.key, $10.key, sizeof(struct pf_poolhashkey)); if ($10.opts) r.rpool.opts |= $10.opts; if ($10.staticport) { if (r.action != PF_NAT) { yyerror("the 'static-port' option is " "only valid with nat rules"); YYERROR; } if (r.rpool.proxy_port[0] != PF_NAT_PROXY_PORT_LOW && r.rpool.proxy_port[1] != PF_NAT_PROXY_PORT_HIGH) { yyerror("the 'static-port' option can't" " be used when specifying a port" " range"); YYERROR; } r.rpool.proxy_port[0] = 0; r.rpool.proxy_port[1] = 0; } expand_rule(&r, $2, $9 == NULL ? NULL : $9->host, $4, $5.src_os, $5.src.host, $5.src.port, $5.dst.host, $5.dst.port, 0, 0, 0, ""); free($9); } ; binatrule : no BINAT natpasslog interface af proto FROM ipspec toipspec tag tagged rtable redirection { - struct pf_rule binat; + struct pfctl_rule binat; struct pf_pooladdr *pa; if (check_rulestate(PFCTL_STATE_NAT)) YYERROR; if (disallow_urpf_failed($9, "\"urpf-failed\" is not " "permitted as a binat destination")) YYERROR; memset(&binat, 0, sizeof(binat)); if ($1 && $3.b1) { yyerror("\"pass\" not valid with \"no\""); YYERROR; } if ($1) binat.action = PF_NOBINAT; else binat.action = PF_BINAT; binat.natpass = $3.b1; binat.log = $3.b2; binat.logif = $3.w2; binat.af = $5; if (!binat.af && $8 != NULL && $8->af) binat.af = $8->af; if (!binat.af && $9 != NULL && $9->af) binat.af = $9->af; if (!binat.af && $13 != NULL && $13->host) binat.af = $13->host->af; if (!binat.af) { yyerror("address family (inet/inet6) " "undefined"); YYERROR; } if ($4 != NULL) { memcpy(binat.ifname, $4->ifname, sizeof(binat.ifname)); binat.ifnot = $4->not; free($4); } if ($10 != NULL) if (strlcpy(binat.tagname, $10, PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } if ($11.name) if (strlcpy(binat.match_tagname, $11.name, PF_TAG_NAME_SIZE) >= PF_TAG_NAME_SIZE) { yyerror("tag too long, max %u chars", PF_TAG_NAME_SIZE - 1); YYERROR; } binat.match_tag_not = $11.neg; binat.rtableid = $12; if ($6 != NULL) { binat.proto = $6->proto; free($6); } if ($8 != NULL && disallow_table($8, "invalid use of " "table <%s> as the source address of a binat rule")) YYERROR; if ($8 != NULL && disallow_alias($8, "invalid use of " "interface (%s) as the source address of a binat " "rule")) YYERROR; if ($13 != NULL && $13->host != NULL && disallow_table( $13->host, "invalid use of table <%s> as the " "redirect address of a binat rule")) YYERROR; if ($13 != NULL && $13->host != NULL && disallow_alias( $13->host, "invalid use of interface (%s) as the " "redirect address of a binat rule")) YYERROR; if ($8 != NULL) { if ($8->next) { yyerror("multiple binat ip addresses"); YYERROR; } if ($8->addr.type == PF_ADDR_DYNIFTL) $8->af = binat.af; if ($8->af != binat.af) { yyerror("binat ip versions must match"); YYERROR; } if (check_netmask($8, binat.af)) YYERROR; memcpy(&binat.src.addr, &$8->addr, sizeof(binat.src.addr)); free($8); } if ($9 != NULL) { if ($9->next) { yyerror("multiple binat ip addresses"); YYERROR; } if ($9->af != binat.af && $9->af) { yyerror("binat ip versions must match"); YYERROR; } if (check_netmask($9, binat.af)) YYERROR; memcpy(&binat.dst.addr, &$9->addr, sizeof(binat.dst.addr)); binat.dst.neg = $9->not; free($9); } if (binat.action == PF_NOBINAT) { if ($13 != NULL) { yyerror("'no binat' rule does not need" " '->'"); YYERROR; } } else { if ($13 == NULL || $13->host == NULL) { yyerror("'binat' rule requires" " '-> address'"); YYERROR; } remove_invalid_hosts(&$13->host, &binat.af); if (invalid_redirect($13->host, binat.af)) YYERROR; if ($13->host->next != NULL) { yyerror("binat rule must redirect to " "a single address"); YYERROR; } if (check_netmask($13->host, binat.af)) YYERROR; if (!PF_AZERO(&binat.src.addr.v.a.mask, binat.af) && !PF_AEQ(&binat.src.addr.v.a.mask, &$13->host->addr.v.a.mask, binat.af)) { yyerror("'binat' source mask and " "redirect mask must be the same"); YYERROR; } TAILQ_INIT(&binat.rpool.list); pa = calloc(1, sizeof(struct pf_pooladdr)); if (pa == NULL) err(1, "binat: calloc"); pa->addr = $13->host->addr; pa->ifname[0] = 0; TAILQ_INSERT_TAIL(&binat.rpool.list, pa, entries); free($13); } pfctl_append_rule(pf, &binat, ""); } ; tag : /* empty */ { $$ = NULL; } | TAG STRING { $$ = $2; } ; tagged : /* empty */ { $$.neg = 0; $$.name = NULL; } | not TAGGED string { $$.neg = $1; $$.name = $3; } ; rtable : /* empty */ { $$ = -1; } | RTABLE NUMBER { if ($2 < 0 || $2 > rt_tableid_max()) { yyerror("invalid rtable id"); YYERROR; } $$ = $2; } ; route_host : STRING { $$ = calloc(1, sizeof(struct node_host)); if ($$ == NULL) err(1, "route_host: calloc"); $$->ifname = strdup($1); set_ipmask($$, 128); $$->next = NULL; $$->tail = $$; } | '(' STRING host ')' { struct node_host *n; $$ = $3; for (n = $3; n != NULL; n = n->next) n->ifname = strdup($2); } ; route_host_list : route_host optnl { $$ = $1; } | route_host_list comma route_host optnl { if ($1->af == 0) $1->af = $3->af; if ($1->af != $3->af) { yyerror("all pool addresses must be in the " "same address family"); YYERROR; } $1->tail->next = $3; $1->tail = $3->tail; $$ = $1; } ; routespec : route_host { $$ = $1; } | '{' optnl route_host_list '}' { $$ = $3; } ; route : /* empty */ { $$.host = NULL; $$.rt = 0; $$.pool_opts = 0; } | FASTROUTE { /* backwards-compat */ $$.host = NULL; $$.rt = 0; $$.pool_opts = 0; } | ROUTETO routespec pool_opts { $$.host = $2; $$.rt = PF_ROUTETO; $$.pool_opts = $3.type | $3.opts; if ($3.key != NULL) $$.key = $3.key; } | REPLYTO routespec pool_opts { $$.host = $2; $$.rt = PF_REPLYTO; $$.pool_opts = $3.type | $3.opts; if ($3.key != NULL) $$.key = $3.key; } | DUPTO routespec pool_opts { $$.host = $2; $$.rt = PF_DUPTO; $$.pool_opts = $3.type | $3.opts; if ($3.key != NULL) $$.key = $3.key; } ; timeout_spec : STRING NUMBER { if (check_rulestate(PFCTL_STATE_OPTION)) { free($1); YYERROR; } if ($2 < 0 || $2 > UINT_MAX) { yyerror("only positive values permitted"); YYERROR; } if (pfctl_set_timeout(pf, $1, $2, 0) != 0) { yyerror("unknown timeout %s", $1); free($1); YYERROR; } free($1); } | INTERVAL NUMBER { if (check_rulestate(PFCTL_STATE_OPTION)) YYERROR; if ($2 < 0 || $2 > UINT_MAX) { yyerror("only positive values permitted"); YYERROR; } if (pfctl_set_timeout(pf, "interval", $2, 0) != 0) YYERROR; } ; timeout_list : timeout_list comma timeout_spec optnl | timeout_spec optnl ; limit_spec : STRING NUMBER { if (check_rulestate(PFCTL_STATE_OPTION)) { free($1); YYERROR; } if ($2 < 0 || $2 > UINT_MAX) { yyerror("only positive values permitted"); YYERROR; } if (pfctl_set_limit(pf, $1, $2) != 0) { yyerror("unable to set limit %s %u", $1, $2); free($1); YYERROR; } free($1); } ; limit_list : limit_list comma limit_spec optnl | limit_spec optnl ; comma : ',' | /* empty */ ; yesno : NO { $$ = 0; } | STRING { if (!strcmp($1, "yes")) $$ = 1; else { yyerror("invalid value '%s', expected 'yes' " "or 'no'", $1); free($1); YYERROR; } free($1); } ; unaryop : '=' { $$ = PF_OP_EQ; } | '!' '=' { $$ = PF_OP_NE; } | '<' '=' { $$ = PF_OP_LE; } | '<' { $$ = PF_OP_LT; } | '>' '=' { $$ = PF_OP_GE; } | '>' { $$ = PF_OP_GT; } ; %% int yyerror(const char *fmt, ...) { va_list ap; file->errors++; va_start(ap, fmt); fprintf(stderr, "%s:%d: ", file->name, yylval.lineno); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); return (0); } int disallow_table(struct node_host *h, const char *fmt) { for (; h != NULL; h = h->next) if (h->addr.type == PF_ADDR_TABLE) { yyerror(fmt, h->addr.v.tblname); return (1); } return (0); } int disallow_urpf_failed(struct node_host *h, const char *fmt) { for (; h != NULL; h = h->next) if (h->addr.type == PF_ADDR_URPFFAILED) { yyerror(fmt); return (1); } return (0); } int disallow_alias(struct node_host *h, const char *fmt) { for (; h != NULL; h = h->next) if (DYNIF_MULTIADDR(h->addr)) { yyerror(fmt, h->addr.v.tblname); return (1); } return (0); } int -rule_consistent(struct pf_rule *r, int anchor_call) +rule_consistent(struct pfctl_rule *r, int anchor_call) { int problems = 0; switch (r->action) { case PF_PASS: case PF_DROP: case PF_SCRUB: case PF_NOSCRUB: problems = filter_consistent(r, anchor_call); break; case PF_NAT: case PF_NONAT: problems = nat_consistent(r); break; case PF_RDR: case PF_NORDR: problems = rdr_consistent(r); break; case PF_BINAT: case PF_NOBINAT: default: break; } return (problems); } int -filter_consistent(struct pf_rule *r, int anchor_call) +filter_consistent(struct pfctl_rule *r, int anchor_call) { int problems = 0; if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP && (r->src.port_op || r->dst.port_op)) { yyerror("port only applies to tcp/udp"); problems++; } if (r->proto != IPPROTO_ICMP && r->proto != IPPROTO_ICMPV6 && (r->type || r->code)) { yyerror("icmp-type/code only applies to icmp"); problems++; } if (!r->af && (r->type || r->code)) { yyerror("must indicate address family with icmp-type/code"); problems++; } if (r->overload_tblname[0] && r->max_src_conn == 0 && r->max_src_conn_rate.seconds == 0) { yyerror("'overload' requires 'max-src-conn' " "or 'max-src-conn-rate'"); problems++; } if ((r->proto == IPPROTO_ICMP && r->af == AF_INET6) || (r->proto == IPPROTO_ICMPV6 && r->af == AF_INET)) { yyerror("proto %s doesn't match address family %s", r->proto == IPPROTO_ICMP ? "icmp" : "icmp6", r->af == AF_INET ? "inet" : "inet6"); problems++; } if (r->allow_opts && r->action != PF_PASS) { yyerror("allow-opts can only be specified for pass rules"); problems++; } if (r->rule_flag & PFRULE_FRAGMENT && (r->src.port_op || r->dst.port_op || r->flagset || r->type || r->code)) { yyerror("fragments can be filtered only on IP header fields"); problems++; } if (r->rule_flag & PFRULE_RETURNRST && r->proto != IPPROTO_TCP) { yyerror("return-rst can only be applied to TCP rules"); problems++; } if (r->max_src_nodes && !(r->rule_flag & PFRULE_RULESRCTRACK)) { yyerror("max-src-nodes requires 'source-track rule'"); problems++; } if (r->action == PF_DROP && r->keep_state) { yyerror("keep state on block rules doesn't make sense"); problems++; } if (r->rule_flag & PFRULE_STATESLOPPY && (r->keep_state == PF_STATE_MODULATE || r->keep_state == PF_STATE_SYNPROXY)) { yyerror("sloppy state matching cannot be used with " "synproxy state or modulate state"); problems++; } return (-problems); } int -nat_consistent(struct pf_rule *r) +nat_consistent(struct pfctl_rule *r) { return (0); /* yeah! */ } int -rdr_consistent(struct pf_rule *r) +rdr_consistent(struct pfctl_rule *r) { int problems = 0; if (r->proto != IPPROTO_TCP && r->proto != IPPROTO_UDP) { if (r->src.port_op) { yyerror("src port only applies to tcp/udp"); problems++; } if (r->dst.port_op) { yyerror("dst port only applies to tcp/udp"); problems++; } if (r->rpool.proxy_port[0]) { yyerror("rpool port only applies to tcp/udp"); problems++; } } if (r->dst.port_op && r->dst.port_op != PF_OP_EQ && r->dst.port_op != PF_OP_RRG) { yyerror("invalid port operator for rdr destination port"); problems++; } return (-problems); } int process_tabledef(char *name, struct table_opts *opts) { struct pfr_buffer ab; struct node_tinit *ti; unsigned long maxcount; size_t s = sizeof(maxcount); bzero(&ab, sizeof(ab)); ab.pfrb_type = PFRB_ADDRS; SIMPLEQ_FOREACH(ti, &opts->init_nodes, entries) { if (ti->file) if (pfr_buf_load(&ab, ti->file, 0, append_addr)) { if (errno) yyerror("cannot load \"%s\": %s", ti->file, strerror(errno)); else yyerror("file \"%s\" contains bad data", ti->file); goto _error; } if (ti->host) if (append_addr_host(&ab, ti->host, 0, 0)) { yyerror("cannot create address buffer: %s", strerror(errno)); goto _error; } } if (pf->opts & PF_OPT_VERBOSE) print_tabledef(name, opts->flags, opts->init_addr, &opts->init_nodes); if (!(pf->opts & PF_OPT_NOACTION) && pfctl_define_table(name, opts->flags, opts->init_addr, pf->anchor->name, &ab, pf->anchor->ruleset.tticket)) { if (sysctlbyname("net.pf.request_maxcount", &maxcount, &s, NULL, 0) == -1) maxcount = 65535; if (ab.pfrb_size > maxcount) yyerror("cannot define table %s: too many elements.\n" "Consider increasing net.pf.request_maxcount.", name); else yyerror("cannot define table %s: %s", name, pfr_strerror(errno)); goto _error; } pf->tdirty = 1; pfr_buf_clear(&ab); return (0); _error: pfr_buf_clear(&ab); return (-1); } struct keywords { const char *k_name; int k_val; }; /* macro gore, but you should've seen the prior indentation nightmare... */ #define FREE_LIST(T,r) \ do { \ T *p, *node = r; \ while (node != NULL) { \ p = node; \ node = node->next; \ free(p); \ } \ } while (0) #define LOOP_THROUGH(T,n,r,C) \ do { \ T *n; \ if (r == NULL) { \ r = calloc(1, sizeof(T)); \ if (r == NULL) \ err(1, "LOOP: calloc"); \ r->next = NULL; \ } \ n = r; \ while (n != NULL) { \ do { \ C; \ } while (0); \ n = n->next; \ } \ } while (0) void expand_label_str(char *label, size_t len, const char *srch, const char *repl) { char *tmp; char *p, *q; if ((tmp = calloc(1, len)) == NULL) err(1, "expand_label_str: calloc"); p = q = label; while ((q = strstr(p, srch)) != NULL) { *q = '\0'; if ((strlcat(tmp, p, len) >= len) || (strlcat(tmp, repl, len) >= len)) errx(1, "expand_label: label too long"); q += strlen(srch); p = q; } if (strlcat(tmp, p, len) >= len) errx(1, "expand_label: label too long"); strlcpy(label, tmp, len); /* always fits */ free(tmp); } void expand_label_if(const char *name, char *label, size_t len, const char *ifname) { if (strstr(label, name) != NULL) { if (!*ifname) expand_label_str(label, len, name, "any"); else expand_label_str(label, len, name, ifname); } } void expand_label_addr(const char *name, char *label, size_t len, sa_family_t af, struct node_host *h) { char tmp[64], tmp_not[66]; if (strstr(label, name) != NULL) { switch (h->addr.type) { case PF_ADDR_DYNIFTL: snprintf(tmp, sizeof(tmp), "(%s)", h->addr.v.ifname); break; case PF_ADDR_TABLE: snprintf(tmp, sizeof(tmp), "<%s>", h->addr.v.tblname); break; case PF_ADDR_NOROUTE: snprintf(tmp, sizeof(tmp), "no-route"); break; case PF_ADDR_URPFFAILED: snprintf(tmp, sizeof(tmp), "urpf-failed"); break; case PF_ADDR_ADDRMASK: if (!af || (PF_AZERO(&h->addr.v.a.addr, af) && PF_AZERO(&h->addr.v.a.mask, af))) snprintf(tmp, sizeof(tmp), "any"); else { char a[48]; int bits; if (inet_ntop(af, &h->addr.v.a.addr, a, sizeof(a)) == NULL) snprintf(tmp, sizeof(tmp), "?"); else { bits = unmask(&h->addr.v.a.mask, af); if ((af == AF_INET && bits < 32) || (af == AF_INET6 && bits < 128)) snprintf(tmp, sizeof(tmp), "%s/%d", a, bits); else snprintf(tmp, sizeof(tmp), "%s", a); } } break; default: snprintf(tmp, sizeof(tmp), "?"); break; } if (h->not) { snprintf(tmp_not, sizeof(tmp_not), "! %s", tmp); expand_label_str(label, len, name, tmp_not); } else expand_label_str(label, len, name, tmp); } } void expand_label_port(const char *name, char *label, size_t len, struct node_port *port) { char a1[6], a2[6], op[13] = ""; if (strstr(label, name) != NULL) { snprintf(a1, sizeof(a1), "%u", ntohs(port->port[0])); snprintf(a2, sizeof(a2), "%u", ntohs(port->port[1])); if (!port->op) ; else if (port->op == PF_OP_IRG) snprintf(op, sizeof(op), "%s><%s", a1, a2); else if (port->op == PF_OP_XRG) snprintf(op, sizeof(op), "%s<>%s", a1, a2); else if (port->op == PF_OP_EQ) snprintf(op, sizeof(op), "%s", a1); else if (port->op == PF_OP_NE) snprintf(op, sizeof(op), "!=%s", a1); else if (port->op == PF_OP_LT) snprintf(op, sizeof(op), "<%s", a1); else if (port->op == PF_OP_LE) snprintf(op, sizeof(op), "<=%s", a1); else if (port->op == PF_OP_GT) snprintf(op, sizeof(op), ">%s", a1); else if (port->op == PF_OP_GE) snprintf(op, sizeof(op), ">=%s", a1); expand_label_str(label, len, name, op); } } void expand_label_proto(const char *name, char *label, size_t len, u_int8_t proto) { struct protoent *pe; char n[4]; if (strstr(label, name) != NULL) { pe = getprotobynumber(proto); if (pe != NULL) expand_label_str(label, len, name, pe->p_name); else { snprintf(n, sizeof(n), "%u", proto); expand_label_str(label, len, name, n); } } } void expand_label_nr(const char *name, char *label, size_t len) { char n[11]; if (strstr(label, name) != NULL) { snprintf(n, sizeof(n), "%u", pf->anchor->match); expand_label_str(label, len, name, n); } } void expand_label(char *label, size_t len, const char *ifname, sa_family_t af, struct node_host *src_host, struct node_port *src_port, struct node_host *dst_host, struct node_port *dst_port, u_int8_t proto) { expand_label_if("$if", label, len, ifname); expand_label_addr("$srcaddr", label, len, af, src_host); expand_label_addr("$dstaddr", label, len, af, dst_host); expand_label_port("$srcport", label, len, src_port); expand_label_port("$dstport", label, len, dst_port); expand_label_proto("$proto", label, len, proto); expand_label_nr("$nr", label, len); } int expand_altq(struct pf_altq *a, struct node_if *interfaces, struct node_queue *nqueues, struct node_queue_bw bwspec, struct node_queue_opt *opts) { struct pf_altq pa, pb; char qname[PF_QNAME_SIZE]; struct node_queue *n; struct node_queue_bw bw; int errs = 0; if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) { FREE_LIST(struct node_if, interfaces); if (nqueues) FREE_LIST(struct node_queue, nqueues); return (0); } LOOP_THROUGH(struct node_if, interface, interfaces, memcpy(&pa, a, sizeof(struct pf_altq)); if (strlcpy(pa.ifname, interface->ifname, sizeof(pa.ifname)) >= sizeof(pa.ifname)) errx(1, "expand_altq: strlcpy"); if (interface->not) { yyerror("altq on ! is not supported"); errs++; } else { if (eval_pfaltq(pf, &pa, &bwspec, opts)) errs++; else if (pfctl_add_altq(pf, &pa)) errs++; if (pf->opts & PF_OPT_VERBOSE) { print_altq(&pf->paltq->altq, 0, &bwspec, opts); if (nqueues && nqueues->tail) { printf("queue { "); LOOP_THROUGH(struct node_queue, queue, nqueues, printf("%s ", queue->queue); ); printf("}"); } printf("\n"); } if (pa.scheduler == ALTQT_CBQ || pa.scheduler == ALTQT_HFSC) { /* now create a root queue */ memset(&pb, 0, sizeof(struct pf_altq)); if (strlcpy(qname, "root_", sizeof(qname)) >= sizeof(qname)) errx(1, "expand_altq: strlcpy"); if (strlcat(qname, interface->ifname, sizeof(qname)) >= sizeof(qname)) errx(1, "expand_altq: strlcat"); if (strlcpy(pb.qname, qname, sizeof(pb.qname)) >= sizeof(pb.qname)) errx(1, "expand_altq: strlcpy"); if (strlcpy(pb.ifname, interface->ifname, sizeof(pb.ifname)) >= sizeof(pb.ifname)) errx(1, "expand_altq: strlcpy"); pb.qlimit = pa.qlimit; pb.scheduler = pa.scheduler; bw.bw_absolute = pa.ifbandwidth; bw.bw_percent = 0; if (eval_pfqueue(pf, &pb, &bw, opts)) errs++; else if (pfctl_add_altq(pf, &pb)) errs++; } LOOP_THROUGH(struct node_queue, queue, nqueues, n = calloc(1, sizeof(struct node_queue)); if (n == NULL) err(1, "expand_altq: calloc"); if (pa.scheduler == ALTQT_CBQ || pa.scheduler == ALTQT_HFSC) if (strlcpy(n->parent, qname, sizeof(n->parent)) >= sizeof(n->parent)) errx(1, "expand_altq: strlcpy"); if (strlcpy(n->queue, queue->queue, sizeof(n->queue)) >= sizeof(n->queue)) errx(1, "expand_altq: strlcpy"); if (strlcpy(n->ifname, interface->ifname, sizeof(n->ifname)) >= sizeof(n->ifname)) errx(1, "expand_altq: strlcpy"); n->scheduler = pa.scheduler; n->next = NULL; n->tail = n; if (queues == NULL) queues = n; else { queues->tail->next = n; queues->tail = n; } ); } ); FREE_LIST(struct node_if, interfaces); if (nqueues) FREE_LIST(struct node_queue, nqueues); return (errs); } int expand_queue(struct pf_altq *a, struct node_if *interfaces, struct node_queue *nqueues, struct node_queue_bw bwspec, struct node_queue_opt *opts) { struct node_queue *n, *nq; struct pf_altq pa; u_int8_t found = 0; u_int8_t errs = 0; if ((pf->loadopt & PFCTL_FLAG_ALTQ) == 0) { FREE_LIST(struct node_queue, nqueues); return (0); } if (queues == NULL) { yyerror("queue %s has no parent", a->qname); FREE_LIST(struct node_queue, nqueues); return (1); } LOOP_THROUGH(struct node_if, interface, interfaces, LOOP_THROUGH(struct node_queue, tqueue, queues, if (!strncmp(a->qname, tqueue->queue, PF_QNAME_SIZE) && (interface->ifname[0] == 0 || (!interface->not && !strncmp(interface->ifname, tqueue->ifname, IFNAMSIZ)) || (interface->not && strncmp(interface->ifname, tqueue->ifname, IFNAMSIZ)))) { /* found ourself in queues */ found++; memcpy(&pa, a, sizeof(struct pf_altq)); if (pa.scheduler != ALTQT_NONE && pa.scheduler != tqueue->scheduler) { yyerror("exactly one scheduler type " "per interface allowed"); return (1); } pa.scheduler = tqueue->scheduler; /* scheduler dependent error checking */ switch (pa.scheduler) { case ALTQT_PRIQ: if (nqueues != NULL) { yyerror("priq queues cannot " "have child queues"); return (1); } if (bwspec.bw_absolute > 0 || bwspec.bw_percent < 100) { yyerror("priq doesn't take " "bandwidth"); return (1); } break; default: break; } if (strlcpy(pa.ifname, tqueue->ifname, sizeof(pa.ifname)) >= sizeof(pa.ifname)) errx(1, "expand_queue: strlcpy"); if (strlcpy(pa.parent, tqueue->parent, sizeof(pa.parent)) >= sizeof(pa.parent)) errx(1, "expand_queue: strlcpy"); if (eval_pfqueue(pf, &pa, &bwspec, opts)) errs++; else if (pfctl_add_altq(pf, &pa)) errs++; for (nq = nqueues; nq != NULL; nq = nq->next) { if (!strcmp(a->qname, nq->queue)) { yyerror("queue cannot have " "itself as child"); errs++; continue; } n = calloc(1, sizeof(struct node_queue)); if (n == NULL) err(1, "expand_queue: calloc"); if (strlcpy(n->parent, a->qname, sizeof(n->parent)) >= sizeof(n->parent)) errx(1, "expand_queue strlcpy"); if (strlcpy(n->queue, nq->queue, sizeof(n->queue)) >= sizeof(n->queue)) errx(1, "expand_queue strlcpy"); if (strlcpy(n->ifname, tqueue->ifname, sizeof(n->ifname)) >= sizeof(n->ifname)) errx(1, "expand_queue strlcpy"); n->scheduler = tqueue->scheduler; n->next = NULL; n->tail = n; if (queues == NULL) queues = n; else { queues->tail->next = n; queues->tail = n; } } if ((pf->opts & PF_OPT_VERBOSE) && ( (found == 1 && interface->ifname[0] == 0) || (found > 0 && interface->ifname[0] != 0))) { print_queue(&pf->paltq->altq, 0, &bwspec, interface->ifname[0] != 0, opts); if (nqueues && nqueues->tail) { printf("{ "); LOOP_THROUGH(struct node_queue, queue, nqueues, printf("%s ", queue->queue); ); printf("}"); } printf("\n"); } } ); ); FREE_LIST(struct node_queue, nqueues); FREE_LIST(struct node_if, interfaces); if (!found) { yyerror("queue %s has no parent", a->qname); errs++; } if (errs) return (1); else return (0); } void -expand_rule(struct pf_rule *r, +expand_rule(struct pfctl_rule *r, struct node_if *interfaces, struct node_host *rpool_hosts, struct node_proto *protos, struct node_os *src_oses, struct node_host *src_hosts, struct node_port *src_ports, struct node_host *dst_hosts, struct node_port *dst_ports, struct node_uid *uids, struct node_gid *gids, struct node_icmp *icmp_types, const char *anchor_call) { sa_family_t af = r->af; int added = 0, error = 0; char ifname[IF_NAMESIZE]; char label[PF_RULE_LABEL_SIZE]; char tagname[PF_TAG_NAME_SIZE]; char match_tagname[PF_TAG_NAME_SIZE]; struct pf_pooladdr *pa; struct node_host *h; u_int8_t flags, flagset, keep_state; if (strlcpy(label, r->label, sizeof(label)) >= sizeof(label)) errx(1, "expand_rule: strlcpy"); if (strlcpy(tagname, r->tagname, sizeof(tagname)) >= sizeof(tagname)) errx(1, "expand_rule: strlcpy"); if (strlcpy(match_tagname, r->match_tagname, sizeof(match_tagname)) >= sizeof(match_tagname)) errx(1, "expand_rule: strlcpy"); flags = r->flags; flagset = r->flagset; keep_state = r->keep_state; LOOP_THROUGH(struct node_if, interface, interfaces, LOOP_THROUGH(struct node_proto, proto, protos, LOOP_THROUGH(struct node_icmp, icmp_type, icmp_types, LOOP_THROUGH(struct node_host, src_host, src_hosts, LOOP_THROUGH(struct node_port, src_port, src_ports, LOOP_THROUGH(struct node_os, src_os, src_oses, LOOP_THROUGH(struct node_host, dst_host, dst_hosts, LOOP_THROUGH(struct node_port, dst_port, dst_ports, LOOP_THROUGH(struct node_uid, uid, uids, LOOP_THROUGH(struct node_gid, gid, gids, r->af = af; /* for link-local IPv6 address, interface must match up */ if ((r->af && src_host->af && r->af != src_host->af) || (r->af && dst_host->af && r->af != dst_host->af) || (src_host->af && dst_host->af && src_host->af != dst_host->af) || (src_host->ifindex && dst_host->ifindex && src_host->ifindex != dst_host->ifindex) || (src_host->ifindex && *interface->ifname && src_host->ifindex != if_nametoindex(interface->ifname)) || (dst_host->ifindex && *interface->ifname && dst_host->ifindex != if_nametoindex(interface->ifname))) continue; if (!r->af && src_host->af) r->af = src_host->af; else if (!r->af && dst_host->af) r->af = dst_host->af; if (*interface->ifname) strlcpy(r->ifname, interface->ifname, sizeof(r->ifname)); else if (if_indextoname(src_host->ifindex, ifname)) strlcpy(r->ifname, ifname, sizeof(r->ifname)); else if (if_indextoname(dst_host->ifindex, ifname)) strlcpy(r->ifname, ifname, sizeof(r->ifname)); else memset(r->ifname, '\0', sizeof(r->ifname)); if (strlcpy(r->label, label, sizeof(r->label)) >= sizeof(r->label)) errx(1, "expand_rule: strlcpy"); if (strlcpy(r->tagname, tagname, sizeof(r->tagname)) >= sizeof(r->tagname)) errx(1, "expand_rule: strlcpy"); if (strlcpy(r->match_tagname, match_tagname, sizeof(r->match_tagname)) >= sizeof(r->match_tagname)) errx(1, "expand_rule: strlcpy"); expand_label(r->label, PF_RULE_LABEL_SIZE, r->ifname, r->af, src_host, src_port, dst_host, dst_port, proto->proto); expand_label(r->tagname, PF_TAG_NAME_SIZE, r->ifname, r->af, src_host, src_port, dst_host, dst_port, proto->proto); expand_label(r->match_tagname, PF_TAG_NAME_SIZE, r->ifname, r->af, src_host, src_port, dst_host, dst_port, proto->proto); error += check_netmask(src_host, r->af); error += check_netmask(dst_host, r->af); r->ifnot = interface->not; r->proto = proto->proto; r->src.addr = src_host->addr; r->src.neg = src_host->not; r->src.port[0] = src_port->port[0]; r->src.port[1] = src_port->port[1]; r->src.port_op = src_port->op; r->dst.addr = dst_host->addr; r->dst.neg = dst_host->not; r->dst.port[0] = dst_port->port[0]; r->dst.port[1] = dst_port->port[1]; r->dst.port_op = dst_port->op; r->uid.op = uid->op; r->uid.uid[0] = uid->uid[0]; r->uid.uid[1] = uid->uid[1]; r->gid.op = gid->op; r->gid.gid[0] = gid->gid[0]; r->gid.gid[1] = gid->gid[1]; r->type = icmp_type->type; r->code = icmp_type->code; if ((keep_state == PF_STATE_MODULATE || keep_state == PF_STATE_SYNPROXY) && r->proto && r->proto != IPPROTO_TCP) r->keep_state = PF_STATE_NORMAL; else r->keep_state = keep_state; if (r->proto && r->proto != IPPROTO_TCP) { r->flags = 0; r->flagset = 0; } else { r->flags = flags; r->flagset = flagset; } if (icmp_type->proto && r->proto != icmp_type->proto) { yyerror("icmp-type mismatch"); error++; } if (src_os && src_os->os) { r->os_fingerprint = pfctl_get_fingerprint(src_os->os); if ((pf->opts & PF_OPT_VERBOSE2) && r->os_fingerprint == PF_OSFP_NOMATCH) fprintf(stderr, "warning: unknown '%s' OS fingerprint\n", src_os->os); } else { r->os_fingerprint = PF_OSFP_ANY; } TAILQ_INIT(&r->rpool.list); for (h = rpool_hosts; h != NULL; h = h->next) { pa = calloc(1, sizeof(struct pf_pooladdr)); if (pa == NULL) err(1, "expand_rule: calloc"); pa->addr = h->addr; if (h->ifname != NULL) { if (strlcpy(pa->ifname, h->ifname, sizeof(pa->ifname)) >= sizeof(pa->ifname)) errx(1, "expand_rule: strlcpy"); } else pa->ifname[0] = 0; TAILQ_INSERT_TAIL(&r->rpool.list, pa, entries); } if (rule_consistent(r, anchor_call[0]) < 0 || error) yyerror("skipping rule due to errors"); else { r->nr = pf->astack[pf->asd]->match++; pfctl_append_rule(pf, r, anchor_call); added++; } )))))))))); FREE_LIST(struct node_if, interfaces); FREE_LIST(struct node_proto, protos); FREE_LIST(struct node_host, src_hosts); FREE_LIST(struct node_port, src_ports); FREE_LIST(struct node_os, src_oses); FREE_LIST(struct node_host, dst_hosts); FREE_LIST(struct node_port, dst_ports); FREE_LIST(struct node_uid, uids); FREE_LIST(struct node_gid, gids); FREE_LIST(struct node_icmp, icmp_types); FREE_LIST(struct node_host, rpool_hosts); if (!added) yyerror("rule expands to no valid combination"); } int expand_skip_interface(struct node_if *interfaces) { int errs = 0; if (!interfaces || (!interfaces->next && !interfaces->not && !strcmp(interfaces->ifname, "none"))) { if (pf->opts & PF_OPT_VERBOSE) printf("set skip on none\n"); errs = pfctl_set_interface_flags(pf, "", PFI_IFLAG_SKIP, 0); return (errs); } if (pf->opts & PF_OPT_VERBOSE) printf("set skip on {"); LOOP_THROUGH(struct node_if, interface, interfaces, if (pf->opts & PF_OPT_VERBOSE) printf(" %s", interface->ifname); if (interface->not) { yyerror("skip on ! is not supported"); errs++; } else errs += pfctl_set_interface_flags(pf, interface->ifname, PFI_IFLAG_SKIP, 1); ); if (pf->opts & PF_OPT_VERBOSE) printf(" }\n"); FREE_LIST(struct node_if, interfaces); if (errs) return (1); else return (0); } #undef FREE_LIST #undef LOOP_THROUGH int check_rulestate(int desired_state) { if (require_order && (rulestate > desired_state)) { yyerror("Rules must be in order: options, normalization, " "queueing, translation, filtering"); return (1); } rulestate = desired_state; return (0); } int kw_cmp(const void *k, const void *e) { return (strcmp(k, ((const struct keywords *)e)->k_name)); } int lookup(char *s) { /* this has to be sorted always */ static const struct keywords keywords[] = { { "all", ALL}, { "allow-opts", ALLOWOPTS}, { "altq", ALTQ}, { "anchor", ANCHOR}, { "antispoof", ANTISPOOF}, { "any", ANY}, { "bandwidth", BANDWIDTH}, { "binat", BINAT}, { "binat-anchor", BINATANCHOR}, { "bitmask", BITMASK}, { "block", BLOCK}, { "block-policy", BLOCKPOLICY}, { "buckets", BUCKETS}, { "cbq", CBQ}, { "code", CODE}, { "codelq", CODEL}, { "crop", FRAGCROP}, { "debug", DEBUG}, { "divert-reply", DIVERTREPLY}, { "divert-to", DIVERTTO}, { "drop", DROP}, { "drop-ovl", FRAGDROP}, { "dup-to", DUPTO}, { "fail-policy", FAILPOLICY}, { "fairq", FAIRQ}, { "fastroute", FASTROUTE}, { "file", FILENAME}, { "fingerprints", FINGERPRINTS}, { "flags", FLAGS}, { "floating", FLOATING}, { "flush", FLUSH}, { "for", FOR}, { "fragment", FRAGMENT}, { "from", FROM}, { "global", GLOBAL}, { "group", GROUP}, { "hfsc", HFSC}, { "hogs", HOGS}, { "hostid", HOSTID}, { "icmp-type", ICMPTYPE}, { "icmp6-type", ICMP6TYPE}, { "if-bound", IFBOUND}, { "in", IN}, { "include", INCLUDE}, { "inet", INET}, { "inet6", INET6}, { "interval", INTERVAL}, { "keep", KEEP}, { "label", LABEL}, { "limit", LIMIT}, { "linkshare", LINKSHARE}, { "load", LOAD}, { "log", LOG}, { "loginterface", LOGINTERFACE}, { "max", MAXIMUM}, { "max-mss", MAXMSS}, { "max-src-conn", MAXSRCCONN}, { "max-src-conn-rate", MAXSRCCONNRATE}, { "max-src-nodes", MAXSRCNODES}, { "max-src-states", MAXSRCSTATES}, { "min-ttl", MINTTL}, { "modulate", MODULATE}, { "nat", NAT}, { "nat-anchor", NATANCHOR}, { "no", NO}, { "no-df", NODF}, { "no-route", NOROUTE}, { "no-sync", NOSYNC}, { "on", ON}, { "optimization", OPTIMIZATION}, { "os", OS}, { "out", OUT}, { "overload", OVERLOAD}, { "pass", PASS}, { "port", PORT}, { "prio", PRIO}, { "priority", PRIORITY}, { "priq", PRIQ}, { "probability", PROBABILITY}, { "proto", PROTO}, { "qlimit", QLIMIT}, { "queue", QUEUE}, { "quick", QUICK}, { "random", RANDOM}, { "random-id", RANDOMID}, { "rdr", RDR}, { "rdr-anchor", RDRANCHOR}, { "realtime", REALTIME}, { "reassemble", REASSEMBLE}, { "reply-to", REPLYTO}, { "require-order", REQUIREORDER}, { "return", RETURN}, { "return-icmp", RETURNICMP}, { "return-icmp6", RETURNICMP6}, { "return-rst", RETURNRST}, { "round-robin", ROUNDROBIN}, { "route", ROUTE}, { "route-to", ROUTETO}, { "rtable", RTABLE}, { "rule", RULE}, { "ruleset-optimization", RULESET_OPTIMIZATION}, { "scrub", SCRUB}, { "set", SET}, { "set-tos", SETTOS}, { "skip", SKIP}, { "sloppy", SLOPPY}, { "source-hash", SOURCEHASH}, { "source-track", SOURCETRACK}, { "state", STATE}, { "state-defaults", STATEDEFAULTS}, { "state-policy", STATEPOLICY}, { "static-port", STATICPORT}, { "sticky-address", STICKYADDRESS}, { "synproxy", SYNPROXY}, { "table", TABLE}, { "tag", TAG}, { "tagged", TAGGED}, { "target", TARGET}, { "tbrsize", TBRSIZE}, { "timeout", TIMEOUT}, { "to", TO}, { "tos", TOS}, { "ttl", TTL}, { "upperlimit", UPPERLIMIT}, { "urpf-failed", URPFFAILED}, { "user", USER}, }; const struct keywords *p; p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), sizeof(keywords[0]), kw_cmp); if (p) { if (debug > 1) fprintf(stderr, "%s: %d\n", s, p->k_val); return (p->k_val); } else { if (debug > 1) fprintf(stderr, "string: %s\n", s); return (STRING); } } #define MAXPUSHBACK 128 static char *parsebuf; static int parseindex; static char pushback_buffer[MAXPUSHBACK]; static int pushback_index = 0; int lgetc(int quotec) { int c, next; if (parsebuf) { /* Read character from the parsebuffer instead of input. */ if (parseindex >= 0) { c = parsebuf[parseindex++]; if (c != '\0') return (c); parsebuf = NULL; } else parseindex++; } if (pushback_index) return (pushback_buffer[--pushback_index]); if (quotec) { if ((c = getc(file->stream)) == EOF) { yyerror("reached end of file while parsing quoted string"); if (popfile() == EOF) return (EOF); return (quotec); } return (c); } while ((c = getc(file->stream)) == '\\') { next = getc(file->stream); if (next != '\n') { c = next; break; } yylval.lineno = file->lineno; file->lineno++; } while (c == EOF) { if (popfile() == EOF) return (EOF); c = getc(file->stream); } return (c); } int lungetc(int c) { if (c == EOF) return (EOF); if (parsebuf) { parseindex--; if (parseindex >= 0) return (c); } if (pushback_index < MAXPUSHBACK-1) return (pushback_buffer[pushback_index++] = c); else return (EOF); } int findeol(void) { int c; parsebuf = NULL; /* skip to either EOF or the first real EOL */ while (1) { if (pushback_index) c = pushback_buffer[--pushback_index]; else c = lgetc(0); if (c == '\n') { file->lineno++; break; } if (c == EOF) break; } return (ERROR); } int yylex(void) { char buf[8096]; char *p, *val; int quotec, next, c; int token; top: p = buf; while ((c = lgetc(0)) == ' ' || c == '\t') ; /* nothing */ yylval.lineno = file->lineno; if (c == '#') while ((c = lgetc(0)) != '\n' && c != EOF) ; /* nothing */ if (c == '$' && parsebuf == NULL) { while (1) { if ((c = lgetc(0)) == EOF) return (0); if (p + 1 >= buf + sizeof(buf) - 1) { yyerror("string too long"); return (findeol()); } if (isalnum(c) || c == '_') { *p++ = (char)c; continue; } *p = '\0'; lungetc(c); break; } val = symget(buf); if (val == NULL) { yyerror("macro '%s' not defined", buf); return (findeol()); } parsebuf = val; parseindex = 0; goto top; } switch (c) { case '\'': case '"': quotec = c; while (1) { if ((c = lgetc(quotec)) == EOF) return (0); if (c == '\n') { file->lineno++; continue; } else if (c == '\\') { if ((next = lgetc(quotec)) == EOF) return (0); if (next == quotec || c == ' ' || c == '\t') c = next; else if (next == '\n') { file->lineno++; continue; } else lungetc(next); } else if (c == quotec) { *p = '\0'; break; } if (p + 1 >= buf + sizeof(buf) - 1) { yyerror("string too long"); return (findeol()); } *p++ = (char)c; } yylval.v.string = strdup(buf); if (yylval.v.string == NULL) err(1, "yylex: strdup"); return (STRING); case '<': next = lgetc(0); if (next == '>') { yylval.v.i = PF_OP_XRG; return (PORTBINARY); } lungetc(next); break; case '>': next = lgetc(0); if (next == '<') { yylval.v.i = PF_OP_IRG; return (PORTBINARY); } lungetc(next); break; case '-': next = lgetc(0); if (next == '>') return (ARROW); lungetc(next); break; } #define allowed_to_end_number(x) \ (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') if (c == '-' || isdigit(c)) { do { *p++ = c; if ((unsigned)(p-buf) >= sizeof(buf)) { yyerror("string too long"); return (findeol()); } } while ((c = lgetc(0)) != EOF && isdigit(c)); lungetc(c); if (p == buf + 1 && buf[0] == '-') goto nodigits; if (c == EOF || allowed_to_end_number(c)) { const char *errstr = NULL; *p = '\0'; yylval.v.number = strtonum(buf, LLONG_MIN, LLONG_MAX, &errstr); if (errstr) { yyerror("\"%s\" invalid number: %s", buf, errstr); return (findeol()); } return (NUMBER); } else { nodigits: while (p > buf + 1) lungetc(*--p); c = *--p; if (c == '-') return (c); } } #define allowed_in_string(x) \ (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ x != '{' && x != '}' && x != '<' && x != '>' && \ x != '!' && x != '=' && x != '/' && x != '#' && \ x != ',')) if (isalnum(c) || c == ':' || c == '_') { do { *p++ = c; if ((unsigned)(p-buf) >= sizeof(buf)) { yyerror("string too long"); return (findeol()); } } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); lungetc(c); *p = '\0'; if ((token = lookup(buf)) == STRING) if ((yylval.v.string = strdup(buf)) == NULL) err(1, "yylex: strdup"); return (token); } if (c == '\n') { yylval.lineno = file->lineno; file->lineno++; } if (c == EOF) return (0); return (c); } int check_file_secrecy(int fd, const char *fname) { struct stat st; if (fstat(fd, &st)) { warn("cannot stat %s", fname); return (-1); } if (st.st_uid != 0 && st.st_uid != getuid()) { warnx("%s: owner not root or current user", fname); return (-1); } if (st.st_mode & (S_IRWXG | S_IRWXO)) { warnx("%s: group/world readable/writeable", fname); return (-1); } return (0); } struct file * pushfile(const char *name, int secret) { struct file *nfile; if ((nfile = calloc(1, sizeof(struct file))) == NULL || (nfile->name = strdup(name)) == NULL) { warn("malloc"); return (NULL); } if (TAILQ_FIRST(&files) == NULL && strcmp(nfile->name, "-") == 0) { nfile->stream = stdin; free(nfile->name); if ((nfile->name = strdup("stdin")) == NULL) { warn("strdup"); free(nfile); return (NULL); } } else if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { warn("%s", nfile->name); free(nfile->name); free(nfile); return (NULL); } else if (secret && check_file_secrecy(fileno(nfile->stream), nfile->name)) { fclose(nfile->stream); free(nfile->name); free(nfile); return (NULL); } nfile->lineno = 1; TAILQ_INSERT_TAIL(&files, nfile, entry); return (nfile); } int popfile(void) { struct file *prev; if ((prev = TAILQ_PREV(file, files, entry)) != NULL) { prev->errors += file->errors; TAILQ_REMOVE(&files, file, entry); fclose(file->stream); free(file->name); free(file); file = prev; return (0); } return (EOF); } int parse_config(char *filename, struct pfctl *xpf) { int errors = 0; struct sym *sym; pf = xpf; errors = 0; rulestate = PFCTL_STATE_NONE; returnicmpdefault = (ICMP_UNREACH << 8) | ICMP_UNREACH_PORT; returnicmp6default = (ICMP6_DST_UNREACH << 8) | ICMP6_DST_UNREACH_NOPORT; blockpolicy = PFRULE_DROP; failpolicy = PFRULE_DROP; require_order = 1; if ((file = pushfile(filename, 0)) == NULL) { warn("cannot open the main config file!"); return (-1); } yyparse(); errors = file->errors; popfile(); /* Free macros and check which have not been used. */ while ((sym = TAILQ_FIRST(&symhead))) { if ((pf->opts & PF_OPT_VERBOSE2) && !sym->used) fprintf(stderr, "warning: macro '%s' not " "used\n", sym->nam); free(sym->nam); free(sym->val); TAILQ_REMOVE(&symhead, sym, entry); free(sym); } return (errors ? -1 : 0); } int symset(const char *nam, const char *val, int persist) { struct sym *sym; for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); sym = TAILQ_NEXT(sym, entry)) ; /* nothing */ if (sym != NULL) { if (sym->persist == 1) return (0); else { free(sym->nam); free(sym->val); TAILQ_REMOVE(&symhead, sym, entry); free(sym); } } if ((sym = calloc(1, sizeof(*sym))) == NULL) return (-1); sym->nam = strdup(nam); if (sym->nam == NULL) { free(sym); return (-1); } sym->val = strdup(val); if (sym->val == NULL) { free(sym->nam); free(sym); return (-1); } sym->used = 0; sym->persist = persist; TAILQ_INSERT_TAIL(&symhead, sym, entry); return (0); } int pfctl_cmdline_symset(char *s) { char *sym, *val; int ret; if ((val = strrchr(s, '=')) == NULL) return (-1); if ((sym = malloc(strlen(s) - strlen(val) + 1)) == NULL) err(1, "pfctl_cmdline_symset: malloc"); strlcpy(sym, s, strlen(s) - strlen(val) + 1); ret = symset(sym, val + 1, 1); free(sym); return (ret); } char * symget(const char *nam) { struct sym *sym; TAILQ_FOREACH(sym, &symhead, entry) if (strcmp(nam, sym->nam) == 0) { sym->used = 1; return (sym->val); } return (NULL); } void -mv_rules(struct pf_ruleset *src, struct pf_ruleset *dst) +mv_rules(struct pfctl_ruleset *src, struct pfctl_ruleset *dst) { int i; - struct pf_rule *r; + struct pfctl_rule *r; for (i = 0; i < PF_RULESET_MAX; ++i) { while ((r = TAILQ_FIRST(src->rules[i].active.ptr)) != NULL) { TAILQ_REMOVE(src->rules[i].active.ptr, r, entries); TAILQ_INSERT_TAIL(dst->rules[i].active.ptr, r, entries); dst->anchor->match++; } src->anchor->match = 0; while ((r = TAILQ_FIRST(src->rules[i].inactive.ptr)) != NULL) { TAILQ_REMOVE(src->rules[i].inactive.ptr, r, entries); TAILQ_INSERT_TAIL(dst->rules[i].inactive.ptr, r, entries); } } } void decide_address_family(struct node_host *n, sa_family_t *af) { if (*af != 0 || n == NULL) return; *af = n->af; while ((n = n->next) != NULL) { if (n->af != *af) { *af = 0; return; } } } void remove_invalid_hosts(struct node_host **nh, sa_family_t *af) { struct node_host *n = *nh, *prev = NULL; while (n != NULL) { if (*af && n->af && n->af != *af) { /* unlink and free n */ struct node_host *next = n->next; /* adjust tail pointer */ if (n == (*nh)->tail) (*nh)->tail = prev; /* adjust previous node's next pointer */ if (prev == NULL) *nh = next; else prev->next = next; /* free node */ if (n->ifname != NULL) free(n->ifname); free(n); n = next; } else { if (n->af && !*af) *af = n->af; prev = n; n = n->next; } } } int invalid_redirect(struct node_host *nh, sa_family_t af) { if (!af) { struct node_host *n; /* tables and dyniftl are ok without an address family */ for (n = nh; n != NULL; n = n->next) { if (n->addr.type != PF_ADDR_TABLE && n->addr.type != PF_ADDR_DYNIFTL) { yyerror("address family not given and " "translation address expands to multiple " "address families"); return (1); } } } if (nh == NULL) { yyerror("no translation address with matching address family " "found."); return (1); } return (0); } int atoul(char *s, u_long *ulvalp) { u_long ulval; char *ep; errno = 0; ulval = strtoul(s, &ep, 0); if (s[0] == '\0' || *ep != '\0') return (-1); if (errno == ERANGE && ulval == ULONG_MAX) return (-1); *ulvalp = ulval; return (0); } int getservice(char *n) { struct servent *s; u_long ulval; if (atoul(n, &ulval) == 0) { if (ulval > 65535) { yyerror("illegal port value %lu", ulval); return (-1); } return (htons(ulval)); } else { s = getservbyname(n, "tcp"); if (s == NULL) s = getservbyname(n, "udp"); if (s == NULL) { yyerror("unknown port %s", n); return (-1); } return (s->s_port); } } int -rule_label(struct pf_rule *r, char *s) +rule_label(struct pfctl_rule *r, char *s) { if (s) { if (strlcpy(r->label, s, sizeof(r->label)) >= sizeof(r->label)) { yyerror("rule label too long (max %d chars)", sizeof(r->label)-1); return (-1); } } return (0); } u_int16_t parseicmpspec(char *w, sa_family_t af) { const struct icmpcodeent *p; u_long ulval; u_int8_t icmptype; if (af == AF_INET) icmptype = returnicmpdefault >> 8; else icmptype = returnicmp6default >> 8; if (atoul(w, &ulval) == -1) { if ((p = geticmpcodebyname(icmptype, w, af)) == NULL) { yyerror("unknown icmp code %s", w); return (0); } ulval = p->code; } if (ulval > 255) { yyerror("invalid icmp code %lu", ulval); return (0); } return (icmptype << 8 | ulval); } int parseport(char *port, struct range *r, int extensions) { char *p = strchr(port, ':'); if (p == NULL) { if ((r->a = getservice(port)) == -1) return (-1); r->b = 0; r->t = PF_OP_NONE; return (0); } if ((extensions & PPORT_STAR) && !strcmp(p+1, "*")) { *p = 0; if ((r->a = getservice(port)) == -1) return (-1); r->b = 0; r->t = PF_OP_IRG; return (0); } if ((extensions & PPORT_RANGE)) { *p++ = 0; if ((r->a = getservice(port)) == -1 || (r->b = getservice(p)) == -1) return (-1); if (r->a == r->b) { r->b = 0; r->t = PF_OP_NONE; } else r->t = PF_OP_RRG; return (0); } return (-1); } int pfctl_load_anchors(int dev, struct pfctl *pf, struct pfr_buffer *trans) { struct loadanchors *la; TAILQ_FOREACH(la, &loadanchorshead, entries) { if (pf->opts & PF_OPT_VERBOSE) fprintf(stderr, "\nLoading anchor %s from %s\n", la->anchorname, la->filename); if (pfctl_rules(dev, la->filename, pf->opts, pf->optimize, la->anchorname, trans) == -1) return (-1); } return (0); } int kw_casecmp(const void *k, const void *e) { return (strcasecmp(k, ((const struct keywords *)e)->k_name)); } int map_tos(char *s, int *val) { /* DiffServ Codepoints and other TOS mappings */ const struct keywords toswords[] = { { "af11", IPTOS_DSCP_AF11 }, { "af12", IPTOS_DSCP_AF12 }, { "af13", IPTOS_DSCP_AF13 }, { "af21", IPTOS_DSCP_AF21 }, { "af22", IPTOS_DSCP_AF22 }, { "af23", IPTOS_DSCP_AF23 }, { "af31", IPTOS_DSCP_AF31 }, { "af32", IPTOS_DSCP_AF32 }, { "af33", IPTOS_DSCP_AF33 }, { "af41", IPTOS_DSCP_AF41 }, { "af42", IPTOS_DSCP_AF42 }, { "af43", IPTOS_DSCP_AF43 }, { "critical", IPTOS_PREC_CRITIC_ECP }, { "cs0", IPTOS_DSCP_CS0 }, { "cs1", IPTOS_DSCP_CS1 }, { "cs2", IPTOS_DSCP_CS2 }, { "cs3", IPTOS_DSCP_CS3 }, { "cs4", IPTOS_DSCP_CS4 }, { "cs5", IPTOS_DSCP_CS5 }, { "cs6", IPTOS_DSCP_CS6 }, { "cs7", IPTOS_DSCP_CS7 }, { "ef", IPTOS_DSCP_EF }, { "inetcontrol", IPTOS_PREC_INTERNETCONTROL }, { "lowdelay", IPTOS_LOWDELAY }, { "netcontrol", IPTOS_PREC_NETCONTROL }, { "reliability", IPTOS_RELIABILITY }, { "throughput", IPTOS_THROUGHPUT }, { "va", IPTOS_DSCP_VA } }; const struct keywords *p; p = bsearch(s, toswords, sizeof(toswords)/sizeof(toswords[0]), sizeof(toswords[0]), kw_casecmp); if (p) { *val = p->k_val; return (1); } return (0); } int rt_tableid_max(void) { #ifdef __FreeBSD__ int fibs; size_t l = sizeof(fibs); if (sysctlbyname("net.fibs", &fibs, &l, NULL, 0) == -1) fibs = 16; /* XXX RT_MAXFIBS, at least limit it some. */ /* * As the OpenBSD code only compares > and not >= we need to adjust * here given we only accept values of 0..n and want to avoid #ifdefs * in the grammar. */ return (fibs - 1); #else return (RT_TABLEID_MAX); #endif } diff --git a/sbin/pfctl/pf_ruleset.c b/sbin/pfctl/pf_ruleset.c index baac1deb4fa7..f5cddf481871 100644 --- a/sbin/pfctl/pf_ruleset.c +++ b/sbin/pfctl/pf_ruleset.c @@ -1,345 +1,346 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2002,2003 Henning Brauer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * Effort sponsored in part by the Defense Advanced Research Projects * Agency (DARPA) and Air Force Research Laboratory, Air Force * Materiel Command, USAF, under agreement number F30602-01-2-0537. * * $OpenBSD: pf_ruleset.c,v 1.2 2008/12/18 15:31:37 dhill Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #ifdef INET6 #include #endif /* INET6 */ #include #include #include #include #include #define rs_malloc(x) calloc(1, x) #define rs_free(x) free(x) #include "pfctl.h" #ifdef PFDEBUG #include #define DPFPRINTF(format, x...) fprintf(stderr, format , ##x) #else #define DPFPRINTF(format, x...) ((void)0) #endif /* PFDEBUG */ -struct pf_anchor_global pf_anchors; -struct pf_anchor pf_main_anchor; +struct pfctl_anchor_global pf_anchors; +struct pfctl_anchor pf_main_anchor; #undef V_pf_anchors #define V_pf_anchors pf_anchors #undef pf_main_ruleset #define pf_main_ruleset pf_main_anchor.ruleset -static __inline int pf_anchor_compare(struct pf_anchor *, - struct pf_anchor *); -static struct pf_anchor *pf_find_anchor(const char *); +static __inline int pf_anchor_compare(struct pfctl_anchor *, + struct pfctl_anchor *); +static struct pfctl_anchor *pf_find_anchor(const char *); -RB_GENERATE(pf_anchor_global, pf_anchor, entry_global, pf_anchor_compare); -RB_GENERATE(pf_anchor_node, pf_anchor, entry_node, pf_anchor_compare); +RB_GENERATE(pfctl_anchor_global, pfctl_anchor, entry_global, + pf_anchor_compare); +RB_GENERATE(pfctl_anchor_node, pfctl_anchor, entry_node, pf_anchor_compare); static __inline int -pf_anchor_compare(struct pf_anchor *a, struct pf_anchor *b) +pf_anchor_compare(struct pfctl_anchor *a, struct pfctl_anchor *b) { int c = strcmp(a->path, b->path); return (c ? (c < 0 ? -1 : 1) : 0); } int pf_get_ruleset_number(u_int8_t action) { switch (action) { case PF_SCRUB: case PF_NOSCRUB: return (PF_RULESET_SCRUB); break; case PF_PASS: case PF_DROP: return (PF_RULESET_FILTER); break; case PF_NAT: case PF_NONAT: return (PF_RULESET_NAT); break; case PF_BINAT: case PF_NOBINAT: return (PF_RULESET_BINAT); break; case PF_RDR: case PF_NORDR: return (PF_RULESET_RDR); break; default: return (PF_RULESET_MAX); break; } } void -pf_init_ruleset(struct pf_ruleset *ruleset) +pf_init_ruleset(struct pfctl_ruleset *ruleset) { int i; - memset(ruleset, 0, sizeof(struct pf_ruleset)); + memset(ruleset, 0, sizeof(struct pfctl_ruleset)); for (i = 0; i < PF_RULESET_MAX; i++) { TAILQ_INIT(&ruleset->rules[i].queues[0]); TAILQ_INIT(&ruleset->rules[i].queues[1]); ruleset->rules[i].active.ptr = &ruleset->rules[i].queues[0]; ruleset->rules[i].inactive.ptr = &ruleset->rules[i].queues[1]; } } -static struct pf_anchor * +static struct pfctl_anchor * pf_find_anchor(const char *path) { - struct pf_anchor *key, *found; + struct pfctl_anchor *key, *found; - key = (struct pf_anchor *)rs_malloc(sizeof(*key)); + key = (struct pfctl_anchor *)rs_malloc(sizeof(*key)); if (key == NULL) return (NULL); strlcpy(key->path, path, sizeof(key->path)); - found = RB_FIND(pf_anchor_global, &V_pf_anchors, key); + found = RB_FIND(pfctl_anchor_global, &V_pf_anchors, key); rs_free(key); return (found); } -struct pf_ruleset * +struct pfctl_ruleset * pf_find_ruleset(const char *path) { - struct pf_anchor *anchor; + struct pfctl_anchor *anchor; while (*path == '/') path++; if (!*path) return (&pf_main_ruleset); anchor = pf_find_anchor(path); if (anchor == NULL) return (NULL); else return (&anchor->ruleset); } -struct pf_ruleset * +struct pfctl_ruleset * pf_find_or_create_ruleset(const char *path) { char *p, *q, *r; - struct pf_ruleset *ruleset; - struct pf_anchor *anchor = NULL, *dup, *parent = NULL; + struct pfctl_ruleset *ruleset; + struct pfctl_anchor *anchor = NULL, *dup, *parent = NULL; if (path[0] == 0) return (&pf_main_ruleset); while (*path == '/') path++; ruleset = pf_find_ruleset(path); if (ruleset != NULL) return (ruleset); p = (char *)rs_malloc(MAXPATHLEN); if (p == NULL) return (NULL); strlcpy(p, path, MAXPATHLEN); while (parent == NULL && (q = strrchr(p, '/')) != NULL) { *q = 0; if ((ruleset = pf_find_ruleset(p)) != NULL) { parent = ruleset->anchor; break; } } if (q == NULL) q = p; else q++; strlcpy(p, path, MAXPATHLEN); if (!*q) { rs_free(p); return (NULL); } while ((r = strchr(q, '/')) != NULL || *q) { if (r != NULL) *r = 0; if (!*q || strlen(q) >= PF_ANCHOR_NAME_SIZE || (parent != NULL && strlen(parent->path) >= MAXPATHLEN - PF_ANCHOR_NAME_SIZE - 1)) { rs_free(p); return (NULL); } - anchor = (struct pf_anchor *)rs_malloc(sizeof(*anchor)); + anchor = (struct pfctl_anchor *)rs_malloc(sizeof(*anchor)); if (anchor == NULL) { rs_free(p); return (NULL); } RB_INIT(&anchor->children); strlcpy(anchor->name, q, sizeof(anchor->name)); if (parent != NULL) { strlcpy(anchor->path, parent->path, sizeof(anchor->path)); strlcat(anchor->path, "/", sizeof(anchor->path)); } strlcat(anchor->path, anchor->name, sizeof(anchor->path)); - if ((dup = RB_INSERT(pf_anchor_global, &V_pf_anchors, anchor)) != + if ((dup = RB_INSERT(pfctl_anchor_global, &V_pf_anchors, anchor)) != NULL) { printf("pf_find_or_create_ruleset: RB_INSERT1 " "'%s' '%s' collides with '%s' '%s'\n", anchor->path, anchor->name, dup->path, dup->name); rs_free(anchor); rs_free(p); return (NULL); } if (parent != NULL) { anchor->parent = parent; - if ((dup = RB_INSERT(pf_anchor_node, &parent->children, + if ((dup = RB_INSERT(pfctl_anchor_node, &parent->children, anchor)) != NULL) { printf("pf_find_or_create_ruleset: " "RB_INSERT2 '%s' '%s' collides with " "'%s' '%s'\n", anchor->path, anchor->name, dup->path, dup->name); - RB_REMOVE(pf_anchor_global, &V_pf_anchors, + RB_REMOVE(pfctl_anchor_global, &V_pf_anchors, anchor); rs_free(anchor); rs_free(p); return (NULL); } } pf_init_ruleset(&anchor->ruleset); anchor->ruleset.anchor = anchor; parent = anchor; if (r != NULL) q = r + 1; else *q = 0; } rs_free(p); return (&anchor->ruleset); } void -pf_remove_if_empty_ruleset(struct pf_ruleset *ruleset) +pf_remove_if_empty_ruleset(struct pfctl_ruleset *ruleset) { - struct pf_anchor *parent; + struct pfctl_anchor *parent; int i; while (ruleset != NULL) { if (ruleset == &pf_main_ruleset || ruleset->anchor == NULL || !RB_EMPTY(&ruleset->anchor->children) || ruleset->anchor->refcnt > 0 || ruleset->tables > 0 || ruleset->topen) return; for (i = 0; i < PF_RULESET_MAX; ++i) if (!TAILQ_EMPTY(ruleset->rules[i].active.ptr) || !TAILQ_EMPTY(ruleset->rules[i].inactive.ptr) || ruleset->rules[i].inactive.open) return; - RB_REMOVE(pf_anchor_global, &V_pf_anchors, ruleset->anchor); + RB_REMOVE(pfctl_anchor_global, &V_pf_anchors, ruleset->anchor); if ((parent = ruleset->anchor->parent) != NULL) - RB_REMOVE(pf_anchor_node, &parent->children, + RB_REMOVE(pfctl_anchor_node, &parent->children, ruleset->anchor); rs_free(ruleset->anchor); if (parent == NULL) return; ruleset = &parent->ruleset; } } int -pf_anchor_setup(struct pf_rule *r, const struct pf_ruleset *s, +pfctl_anchor_setup(struct pfctl_rule *r, const struct pfctl_ruleset *s, const char *name) { char *p, *path; - struct pf_ruleset *ruleset; + struct pfctl_ruleset *ruleset; r->anchor = NULL; r->anchor_relative = 0; r->anchor_wildcard = 0; if (!name[0]) return (0); path = (char *)rs_malloc(MAXPATHLEN); if (path == NULL) return (1); if (name[0] == '/') strlcpy(path, name + 1, MAXPATHLEN); else { /* relative path */ r->anchor_relative = 1; if (s->anchor == NULL || !s->anchor->path[0]) path[0] = 0; else strlcpy(path, s->anchor->path, MAXPATHLEN); while (name[0] == '.' && name[1] == '.' && name[2] == '/') { if (!path[0]) { - printf("pf_anchor_setup: .. beyond root\n"); + printf("pfctl_anchor_setup: .. beyond root\n"); rs_free(path); return (1); } if ((p = strrchr(path, '/')) != NULL) *p = 0; else path[0] = 0; r->anchor_relative++; name += 3; } if (path[0]) strlcat(path, "/", MAXPATHLEN); strlcat(path, name, MAXPATHLEN); } if ((p = strrchr(path, '/')) != NULL && !strcmp(p, "/*")) { r->anchor_wildcard = 1; *p = 0; } ruleset = pf_find_or_create_ruleset(path); rs_free(path); if (ruleset == NULL || ruleset->anchor == NULL) { - printf("pf_anchor_setup: ruleset\n"); + printf("pfctl_anchor_setup: ruleset\n"); return (1); } r->anchor = ruleset->anchor; r->anchor->refcnt++; return (0); } diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c index fde9d61260ef..548aab0f839e 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -1,2512 +1,2515 @@ /* $OpenBSD: pfctl.c,v 1.278 2008/08/31 20:18:17 jmc Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2002,2003 Henning Brauer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include __FBSDID("$FreeBSD$"); #define PFIOC_USE_LATEST #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" void usage(void); int pfctl_enable(int, int); int pfctl_disable(int, int); int pfctl_clear_stats(int, int); int pfctl_get_skip_ifaces(void); int pfctl_check_skip_ifaces(char *); int pfctl_adjust_skip_ifaces(struct pfctl *); int pfctl_clear_interface_flags(int, int); int pfctl_clear_rules(int, int, char *); int pfctl_clear_nat(int, int, char *); int pfctl_clear_altq(int, int); int pfctl_clear_src_nodes(int, int); int pfctl_clear_states(int, const char *, int); void pfctl_addrprefix(char *, struct pf_addr *); int pfctl_kill_src_nodes(int, const char *, int); int pfctl_net_kill_states(int, const char *, int); int pfctl_label_kill_states(int, const char *, int); int pfctl_id_kill_states(int, const char *, int); void pfctl_init_options(struct pfctl *); int pfctl_load_options(struct pfctl *); int pfctl_load_limit(struct pfctl *, unsigned int, unsigned int); int pfctl_load_timeout(struct pfctl *, unsigned int, unsigned int); int pfctl_load_debug(struct pfctl *, unsigned int); int pfctl_load_logif(struct pfctl *, char *); int pfctl_load_hostid(struct pfctl *, u_int32_t); int pfctl_get_pool(int, struct pf_pool *, u_int32_t, u_int32_t, int, char *); -void pfctl_print_rule_counters(struct pf_rule *, int); +void pfctl_print_rule_counters(struct pfctl_rule *, int); int pfctl_show_rules(int, char *, int, enum pfctl_show, char *, int); int pfctl_show_nat(int, int, char *); int pfctl_show_src_nodes(int, int); int pfctl_show_states(int, const char *, int); int pfctl_show_status(int, int); int pfctl_show_running(int); int pfctl_show_timeouts(int, int); int pfctl_show_limits(int, int); void pfctl_debug(int, u_int32_t, int); int pfctl_test_altqsupport(int, int); int pfctl_show_anchors(int, int, char *); -int pfctl_ruleset_trans(struct pfctl *, char *, struct pf_anchor *); +int pfctl_ruleset_trans(struct pfctl *, char *, struct pfctl_anchor *); int pfctl_load_ruleset(struct pfctl *, char *, - struct pf_ruleset *, int, int); -int pfctl_load_rule(struct pfctl *, char *, struct pf_rule *, int); + struct pfctl_ruleset *, int, int); +int pfctl_load_rule(struct pfctl *, char *, struct pfctl_rule *, int); const char *pfctl_lookup_option(char *, const char * const *); -static struct pf_anchor_global pf_anchors; -static struct pf_anchor pf_main_anchor; +static struct pfctl_anchor_global pf_anchors; +static struct pfctl_anchor pf_main_anchor; static struct pfr_buffer skip_b; static const char *clearopt; static char *rulesopt; static const char *showopt; static const char *debugopt; static char *anchoropt; static const char *optiopt = NULL; static const char *pf_device = "/dev/pf"; static char *ifaceopt; static char *tableopt; static const char *tblcmdopt; static int src_node_killers; static char *src_node_kill[2]; static int state_killers; static char *state_kill[2]; int loadopt; int altqsupport; int dev = -1; static int first_title = 1; static int labels = 0; #define INDENT(d, o) do { \ if (o) { \ int i; \ for (i=0; i < d; i++) \ printf(" "); \ } \ } while (0); \ static const struct { const char *name; int index; } pf_limits[] = { { "states", PF_LIMIT_STATES }, { "src-nodes", PF_LIMIT_SRC_NODES }, { "frags", PF_LIMIT_FRAGS }, { "table-entries", PF_LIMIT_TABLE_ENTRIES }, { NULL, 0 } }; struct pf_hint { const char *name; int timeout; }; static const struct pf_hint pf_hint_normal[] = { { "tcp.first", 2 * 60 }, { "tcp.opening", 30 }, { "tcp.established", 24 * 60 * 60 }, { "tcp.closing", 15 * 60 }, { "tcp.finwait", 45 }, { "tcp.closed", 90 }, { "tcp.tsdiff", 30 }, { NULL, 0 } }; static const struct pf_hint pf_hint_satellite[] = { { "tcp.first", 3 * 60 }, { "tcp.opening", 30 + 5 }, { "tcp.established", 24 * 60 * 60 }, { "tcp.closing", 15 * 60 + 5 }, { "tcp.finwait", 45 + 5 }, { "tcp.closed", 90 + 5 }, { "tcp.tsdiff", 60 }, { NULL, 0 } }; static const struct pf_hint pf_hint_conservative[] = { { "tcp.first", 60 * 60 }, { "tcp.opening", 15 * 60 }, { "tcp.established", 5 * 24 * 60 * 60 }, { "tcp.closing", 60 * 60 }, { "tcp.finwait", 10 * 60 }, { "tcp.closed", 3 * 60 }, { "tcp.tsdiff", 60 }, { NULL, 0 } }; static const struct pf_hint pf_hint_aggressive[] = { { "tcp.first", 30 }, { "tcp.opening", 5 }, { "tcp.established", 5 * 60 * 60 }, { "tcp.closing", 60 }, { "tcp.finwait", 30 }, { "tcp.closed", 30 }, { "tcp.tsdiff", 10 }, { NULL, 0 } }; static const struct { const char *name; const struct pf_hint *hint; } pf_hints[] = { { "normal", pf_hint_normal }, { "satellite", pf_hint_satellite }, { "high-latency", pf_hint_satellite }, { "conservative", pf_hint_conservative }, { "aggressive", pf_hint_aggressive }, { NULL, NULL } }; static const char * const clearopt_list[] = { "nat", "queue", "rules", "Sources", "states", "info", "Tables", "osfp", "all", NULL }; static const char * const showopt_list[] = { "nat", "queue", "rules", "Anchors", "Sources", "states", "info", "Interfaces", "labels", "timeouts", "memory", "Tables", "osfp", "Running", "all", NULL }; static const char * const tblcmdopt_list[] = { "kill", "flush", "add", "delete", "load", "replace", "show", "test", "zero", "expire", NULL }; static const char * const debugopt_list[] = { "none", "urgent", "misc", "loud", NULL }; static const char * const optiopt_list[] = { "none", "basic", "profile", NULL }; void usage(void) { extern char *__progname; fprintf(stderr, "usage: %s [-AdeghmNnOPqRrvz] [-a anchor] [-D macro=value] [-F modifier]\n" "\t[-f file] [-i interface] [-K host | network]\n" "\t[-k host | network | label | id] [-o level] [-p device]\n" "\t[-s modifier] [-t table -T command [address ...]] [-x level]\n", __progname); exit(1); } int pfctl_enable(int dev, int opts) { if (ioctl(dev, DIOCSTART)) { if (errno == EEXIST) errx(1, "pf already enabled"); else if (errno == ESRCH) errx(1, "pfil registeration failed"); else err(1, "DIOCSTART"); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf enabled\n"); if (altqsupport && ioctl(dev, DIOCSTARTALTQ)) if (errno != EEXIST) err(1, "DIOCSTARTALTQ"); return (0); } int pfctl_disable(int dev, int opts) { if (ioctl(dev, DIOCSTOP)) { if (errno == ENOENT) errx(1, "pf not enabled"); else err(1, "DIOCSTOP"); } if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf disabled\n"); if (altqsupport && ioctl(dev, DIOCSTOPALTQ)) if (errno != ENOENT) err(1, "DIOCSTOPALTQ"); return (0); } int pfctl_clear_stats(int dev, int opts) { if (ioctl(dev, DIOCCLRSTATUS)) err(1, "DIOCCLRSTATUS"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf: statistics cleared\n"); return (0); } int pfctl_get_skip_ifaces(void) { bzero(&skip_b, sizeof(skip_b)); skip_b.pfrb_type = PFRB_IFACES; for (;;) { pfr_buf_grow(&skip_b, skip_b.pfrb_size); skip_b.pfrb_size = skip_b.pfrb_msize; if (pfi_get_ifaces(NULL, skip_b.pfrb_caddr, &skip_b.pfrb_size)) err(1, "pfi_get_ifaces"); if (skip_b.pfrb_size <= skip_b.pfrb_msize) break; } return (0); } int pfctl_check_skip_ifaces(char *ifname) { struct pfi_kif *p; struct node_host *h = NULL, *n = NULL; PFRB_FOREACH(p, &skip_b) { if (!strcmp(ifname, p->pfik_name) && (p->pfik_flags & PFI_IFLAG_SKIP)) p->pfik_flags &= ~PFI_IFLAG_SKIP; if (!strcmp(ifname, p->pfik_name) && p->pfik_group != NULL) { if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL) continue; for (n = h; n != NULL; n = n->next) { if (p->pfik_ifp == NULL) continue; if (strncmp(p->pfik_name, ifname, IFNAMSIZ)) continue; p->pfik_flags &= ~PFI_IFLAG_SKIP; } } } return (0); } int pfctl_adjust_skip_ifaces(struct pfctl *pf) { struct pfi_kif *p, *pp; struct node_host *h = NULL, *n = NULL; PFRB_FOREACH(p, &skip_b) { if (p->pfik_group == NULL || !(p->pfik_flags & PFI_IFLAG_SKIP)) continue; pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0); if ((h = ifa_grouplookup(p->pfik_name, 0)) == NULL) continue; for (n = h; n != NULL; n = n->next) PFRB_FOREACH(pp, &skip_b) { if (pp->pfik_ifp == NULL) continue; if (strncmp(pp->pfik_name, n->ifname, IFNAMSIZ)) continue; if (!(pp->pfik_flags & PFI_IFLAG_SKIP)) pfctl_set_interface_flags(pf, pp->pfik_name, PFI_IFLAG_SKIP, 1); if (pp->pfik_flags & PFI_IFLAG_SKIP) pp->pfik_flags &= ~PFI_IFLAG_SKIP; } } PFRB_FOREACH(p, &skip_b) { if (p->pfik_ifp == NULL || ! (p->pfik_flags & PFI_IFLAG_SKIP)) continue; pfctl_set_interface_flags(pf, p->pfik_name, PFI_IFLAG_SKIP, 0); } return (0); } int pfctl_clear_interface_flags(int dev, int opts) { struct pfioc_iface pi; if ((opts & PF_OPT_NOACTION) == 0) { bzero(&pi, sizeof(pi)); pi.pfiio_flags = PFI_IFLAG_SKIP; if (ioctl(dev, DIOCCLRIFFLAG, &pi)) err(1, "DIOCCLRIFFLAG"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "pf: interface flags reset\n"); } return (0); } int pfctl_clear_rules(int dev, int opts, char *anchorname) { struct pfr_buffer t; memset(&t, 0, sizeof(t)); t.pfrb_type = PFRB_TRANS; if (pfctl_add_trans(&t, PF_RULESET_SCRUB, anchorname) || pfctl_add_trans(&t, PF_RULESET_FILTER, anchorname) || pfctl_trans(dev, &t, DIOCXBEGIN, 0) || pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) err(1, "pfctl_clear_rules"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "rules cleared\n"); return (0); } int pfctl_clear_nat(int dev, int opts, char *anchorname) { struct pfr_buffer t; memset(&t, 0, sizeof(t)); t.pfrb_type = PFRB_TRANS; if (pfctl_add_trans(&t, PF_RULESET_NAT, anchorname) || pfctl_add_trans(&t, PF_RULESET_BINAT, anchorname) || pfctl_add_trans(&t, PF_RULESET_RDR, anchorname) || pfctl_trans(dev, &t, DIOCXBEGIN, 0) || pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) err(1, "pfctl_clear_nat"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "nat cleared\n"); return (0); } int pfctl_clear_altq(int dev, int opts) { struct pfr_buffer t; if (!altqsupport) return (-1); memset(&t, 0, sizeof(t)); t.pfrb_type = PFRB_TRANS; if (pfctl_add_trans(&t, PF_RULESET_ALTQ, "") || pfctl_trans(dev, &t, DIOCXBEGIN, 0) || pfctl_trans(dev, &t, DIOCXCOMMIT, 0)) err(1, "pfctl_clear_altq"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "altq cleared\n"); return (0); } int pfctl_clear_src_nodes(int dev, int opts) { if (ioctl(dev, DIOCCLRSRCNODES)) err(1, "DIOCCLRSRCNODES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "source tracking entries cleared\n"); return (0); } int pfctl_clear_states(int dev, const char *iface, int opts) { struct pfioc_state_kill psk; memset(&psk, 0, sizeof(psk)); if (iface != NULL && strlcpy(psk.psk_ifname, iface, sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) errx(1, "invalid interface: %s", iface); if (ioctl(dev, DIOCCLRSTATES, &psk)) err(1, "DIOCCLRSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "%d states cleared\n", psk.psk_killed); return (0); } void pfctl_addrprefix(char *addr, struct pf_addr *mask) { char *p; const char *errstr; int prefix, ret_ga, q, r; struct addrinfo hints, *res; if ((p = strchr(addr, '/')) == NULL) return; *p++ = '\0'; prefix = strtonum(p, 0, 128, &errstr); if (errstr) errx(1, "prefix is %s: %s", errstr, p); bzero(&hints, sizeof(hints)); /* prefix only with numeric addresses */ hints.ai_flags |= AI_NUMERICHOST; if ((ret_ga = getaddrinfo(addr, NULL, &hints, &res))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } if (res->ai_family == AF_INET && prefix > 32) errx(1, "prefix too long for AF_INET"); else if (res->ai_family == AF_INET6 && prefix > 128) errx(1, "prefix too long for AF_INET6"); q = prefix >> 3; r = prefix & 7; switch (res->ai_family) { case AF_INET: bzero(&mask->v4, sizeof(mask->v4)); mask->v4.s_addr = htonl((u_int32_t) (0xffffffffffULL << (32 - prefix))); break; case AF_INET6: bzero(&mask->v6, sizeof(mask->v6)); if (q > 0) memset((void *)&mask->v6, 0xff, q); if (r > 0) *((u_char *)&mask->v6 + q) = (0xff00 >> r) & 0xff; break; } freeaddrinfo(res); } int pfctl_kill_src_nodes(int dev, const char *iface, int opts) { struct pfioc_src_node_kill psnk; struct addrinfo *res[2], *resp[2]; struct sockaddr last_src, last_dst; int killed, sources, dests; int ret_ga; killed = sources = dests = 0; memset(&psnk, 0, sizeof(psnk)); memset(&psnk.psnk_src.addr.v.a.mask, 0xff, sizeof(psnk.psnk_src.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(src_node_kill[0], &psnk.psnk_src.addr.v.a.mask); if ((ret_ga = getaddrinfo(src_node_kill[0], NULL, NULL, &res[0]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { if (resp[0]->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp[0]->ai_addr; psnk.psnk_af = resp[0]->ai_family; sources++; if (psnk.psnk_af == AF_INET) psnk.psnk_src.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; else if (psnk.psnk_af == AF_INET6) psnk.psnk_src.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[0]->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", psnk.psnk_af); if (src_node_killers > 1) { dests = 0; memset(&psnk.psnk_dst.addr.v.a.mask, 0xff, sizeof(psnk.psnk_dst.addr.v.a.mask)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(src_node_kill[1], &psnk.psnk_dst.addr.v.a.mask); if ((ret_ga = getaddrinfo(src_node_kill[1], NULL, NULL, &res[1]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[1] = res[1]; resp[1]; resp[1] = resp[1]->ai_next) { if (resp[1]->ai_addr == NULL) continue; if (psnk.psnk_af != resp[1]->ai_family) continue; if (memcmp(&last_dst, resp[1]->ai_addr, sizeof(last_dst)) == 0) continue; last_dst = *(struct sockaddr *)resp[1]->ai_addr; dests++; if (psnk.psnk_af == AF_INET) psnk.psnk_dst.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[1]-> ai_addr)->sin_addr; else if (psnk.psnk_af == AF_INET6) psnk.psnk_dst.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[1]-> ai_addr)->sin6_addr; else errx(1, "Unknown address family %d", psnk.psnk_af); if (ioctl(dev, DIOCKILLSRCNODES, &psnk)) err(1, "DIOCKILLSRCNODES"); killed += psnk.psnk_killed; } freeaddrinfo(res[1]); } else { if (ioctl(dev, DIOCKILLSRCNODES, &psnk)) err(1, "DIOCKILLSRCNODES"); killed += psnk.psnk_killed; } } freeaddrinfo(res[0]); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d src nodes from %d sources and %d " "destinations\n", killed, sources, dests); return (0); } int pfctl_net_kill_states(int dev, const char *iface, int opts) { struct pfioc_state_kill psk; struct addrinfo *res[2], *resp[2]; struct sockaddr last_src, last_dst; int killed, sources, dests; int ret_ga; killed = sources = dests = 0; memset(&psk, 0, sizeof(psk)); memset(&psk.psk_src.addr.v.a.mask, 0xff, sizeof(psk.psk_src.addr.v.a.mask)); memset(&last_src, 0xff, sizeof(last_src)); memset(&last_dst, 0xff, sizeof(last_dst)); if (iface != NULL && strlcpy(psk.psk_ifname, iface, sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) errx(1, "invalid interface: %s", iface); pfctl_addrprefix(state_kill[0], &psk.psk_src.addr.v.a.mask); if ((ret_ga = getaddrinfo(state_kill[0], NULL, NULL, &res[0]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[0] = res[0]; resp[0]; resp[0] = resp[0]->ai_next) { if (resp[0]->ai_addr == NULL) continue; /* We get lots of duplicates. Catch the easy ones */ if (memcmp(&last_src, resp[0]->ai_addr, sizeof(last_src)) == 0) continue; last_src = *(struct sockaddr *)resp[0]->ai_addr; psk.psk_af = resp[0]->ai_family; sources++; if (psk.psk_af == AF_INET) psk.psk_src.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[0]->ai_addr)->sin_addr; else if (psk.psk_af == AF_INET6) psk.psk_src.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[0]->ai_addr)-> sin6_addr; else errx(1, "Unknown address family %d", psk.psk_af); if (state_killers > 1) { dests = 0; memset(&psk.psk_dst.addr.v.a.mask, 0xff, sizeof(psk.psk_dst.addr.v.a.mask)); memset(&last_dst, 0xff, sizeof(last_dst)); pfctl_addrprefix(state_kill[1], &psk.psk_dst.addr.v.a.mask); if ((ret_ga = getaddrinfo(state_kill[1], NULL, NULL, &res[1]))) { errx(1, "getaddrinfo: %s", gai_strerror(ret_ga)); /* NOTREACHED */ } for (resp[1] = res[1]; resp[1]; resp[1] = resp[1]->ai_next) { if (resp[1]->ai_addr == NULL) continue; if (psk.psk_af != resp[1]->ai_family) continue; if (memcmp(&last_dst, resp[1]->ai_addr, sizeof(last_dst)) == 0) continue; last_dst = *(struct sockaddr *)resp[1]->ai_addr; dests++; if (psk.psk_af == AF_INET) psk.psk_dst.addr.v.a.addr.v4 = ((struct sockaddr_in *)resp[1]-> ai_addr)->sin_addr; else if (psk.psk_af == AF_INET6) psk.psk_dst.addr.v.a.addr.v6 = ((struct sockaddr_in6 *)resp[1]-> ai_addr)->sin6_addr; else errx(1, "Unknown address family %d", psk.psk_af); if (ioctl(dev, DIOCKILLSTATES, &psk)) err(1, "DIOCKILLSTATES"); killed += psk.psk_killed; } freeaddrinfo(res[1]); } else { if (ioctl(dev, DIOCKILLSTATES, &psk)) err(1, "DIOCKILLSTATES"); killed += psk.psk_killed; } } freeaddrinfo(res[0]); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states from %d sources and %d " "destinations\n", killed, sources, dests); return (0); } int pfctl_label_kill_states(int dev, const char *iface, int opts) { struct pfioc_state_kill psk; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no label specified"); usage(); } memset(&psk, 0, sizeof(psk)); if (iface != NULL && strlcpy(psk.psk_ifname, iface, sizeof(psk.psk_ifname)) >= sizeof(psk.psk_ifname)) errx(1, "invalid interface: %s", iface); if (strlcpy(psk.psk_label, state_kill[1], sizeof(psk.psk_label)) >= sizeof(psk.psk_label)) errx(1, "label too long: %s", state_kill[1]); if (ioctl(dev, DIOCKILLSTATES, &psk)) err(1, "DIOCKILLSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", psk.psk_killed); return (0); } int pfctl_id_kill_states(int dev, const char *iface, int opts) { struct pfioc_state_kill psk; if (state_killers != 2 || (strlen(state_kill[1]) == 0)) { warnx("no id specified"); usage(); } memset(&psk, 0, sizeof(psk)); if ((sscanf(state_kill[1], "%jx/%x", &psk.psk_pfcmp.id, &psk.psk_pfcmp.creatorid)) == 2) HTONL(psk.psk_pfcmp.creatorid); else if ((sscanf(state_kill[1], "%jx", &psk.psk_pfcmp.id)) == 1) { psk.psk_pfcmp.creatorid = 0; } else { warnx("wrong id format specified"); usage(); } if (psk.psk_pfcmp.id == 0) { warnx("cannot kill id 0"); usage(); } psk.psk_pfcmp.id = htobe64(psk.psk_pfcmp.id); if (ioctl(dev, DIOCKILLSTATES, &psk)) err(1, "DIOCKILLSTATES"); if ((opts & PF_OPT_QUIET) == 0) fprintf(stderr, "killed %d states\n", psk.psk_killed); return (0); } int pfctl_get_pool(int dev, struct pf_pool *pool, u_int32_t nr, u_int32_t ticket, int r_action, char *anchorname) { struct pfioc_pooladdr pp; struct pf_pooladdr *pa; u_int32_t pnr, mpnr; memset(&pp, 0, sizeof(pp)); memcpy(pp.anchor, anchorname, sizeof(pp.anchor)); pp.r_action = r_action; pp.r_num = nr; pp.ticket = ticket; if (ioctl(dev, DIOCGETADDRS, &pp)) { warn("DIOCGETADDRS"); return (-1); } mpnr = pp.nr; TAILQ_INIT(&pool->list); for (pnr = 0; pnr < mpnr; ++pnr) { pp.nr = pnr; if (ioctl(dev, DIOCGETADDR, &pp)) { warn("DIOCGETADDR"); return (-1); } pa = calloc(1, sizeof(struct pf_pooladdr)); if (pa == NULL) err(1, "calloc"); bcopy(&pp.addr, pa, sizeof(struct pf_pooladdr)); TAILQ_INSERT_TAIL(&pool->list, pa, entries); } return (0); } void pfctl_move_pool(struct pf_pool *src, struct pf_pool *dst) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(&src->list)) != NULL) { TAILQ_REMOVE(&src->list, pa, entries); TAILQ_INSERT_TAIL(&dst->list, pa, entries); } } void pfctl_clear_pool(struct pf_pool *pool) { struct pf_pooladdr *pa; while ((pa = TAILQ_FIRST(&pool->list)) != NULL) { TAILQ_REMOVE(&pool->list, pa, entries); free(pa); } } void -pfctl_print_rule_counters(struct pf_rule *rule, int opts) +pfctl_print_rule_counters(struct pfctl_rule *rule, int opts) { if (opts & PF_OPT_DEBUG) { const char *t[PF_SKIP_COUNT] = { "i", "d", "f", "p", "sa", "sp", "da", "dp" }; int i; printf(" [ Skip steps: "); for (i = 0; i < PF_SKIP_COUNT; ++i) { if (rule->skip[i].nr == rule->nr + 1) continue; printf("%s=", t[i]); if (rule->skip[i].nr == -1) printf("end "); else printf("%u ", rule->skip[i].nr); } printf("]\n"); printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n", rule->qname, rule->qid, rule->pqname, rule->pqid); } if (opts & PF_OPT_VERBOSE) { printf(" [ Evaluations: %-8llu Packets: %-8llu " "Bytes: %-10llu States: %-6ju]\n", (unsigned long long)rule->evaluations, (unsigned long long)(rule->packets[0] + rule->packets[1]), (unsigned long long)(rule->bytes[0] + rule->bytes[1]), (uintmax_t)rule->u_states_cur); if (!(opts & PF_OPT_DEBUG)) printf(" [ Inserted: uid %u pid %u " "State Creations: %-6ju]\n", (unsigned)rule->cuid, (unsigned)rule->cpid, (uintmax_t)rule->u_states_tot); } } void pfctl_print_title(char *title) { if (!first_title) printf("\n"); first_title = 0; printf("%s\n", title); } int pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, char *anchorname, int depth) { struct pfioc_rule pr; + struct pfctl_rule rule; u_int32_t nr, mnr, header = 0; int rule_numbers = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); int numeric = opts & PF_OPT_NUMERIC; int len = strlen(path); int brace; char *p; if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", anchorname); else snprintf(&path[len], MAXPATHLEN - len, "%s", anchorname); memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, path, sizeof(pr.anchor)); if (opts & PF_OPT_SHOWALL) { pr.rule.action = PF_PASS; if (ioctl(dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); goto error; } header++; } pr.rule.action = PF_SCRUB; if (ioctl(dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); goto error; } if (opts & PF_OPT_SHOWALL) { if (format == PFCTL_SHOW_RULES && (pr.nr > 0 || header)) pfctl_print_title("FILTER RULES:"); else if (format == PFCTL_SHOW_LABELS && labels) pfctl_print_title("LABEL COUNTERS:"); } mnr = pr.nr; if (opts & PF_OPT_CLRRULECTRS) pr.action = PF_GET_CLR_CNTR; for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; if (pfctl_get_rule(dev, nr, pr.ticket, path, PF_SCRUB, - &pr.rule, pr.anchor_call)) { + &rule, pr.anchor_call)) { warn("DIOCGETRULENV"); goto error; } - if (pfctl_get_pool(dev, &pr.rule.rpool, + if (pfctl_get_pool(dev, &rule.rpool, nr, pr.ticket, PF_SCRUB, path) != 0) goto error; switch (format) { case PFCTL_SHOW_LABELS: break; case PFCTL_SHOW_RULES: - if (pr.rule.label[0] && (opts & PF_OPT_SHOWALL)) + if (rule.label[0] && (opts & PF_OPT_SHOWALL)) labels = 1; - print_rule(&pr.rule, pr.anchor_call, rule_numbers, numeric); + print_rule(&rule, pr.anchor_call, rule_numbers, numeric); printf("\n"); - pfctl_print_rule_counters(&pr.rule, opts); + pfctl_print_rule_counters(&rule, opts); break; case PFCTL_SHOW_NOTHING: break; } - pfctl_clear_pool(&pr.rule.rpool); + pfctl_clear_pool(&rule.rpool); } pr.rule.action = PF_PASS; if (ioctl(dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); goto error; } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; if (pfctl_get_rule(dev, nr, pr.ticket, path, PF_PASS, - &pr.rule, pr.anchor_call)) { + &rule, pr.anchor_call)) { warn("DIOCGETRULE"); goto error; } - if (pfctl_get_pool(dev, &pr.rule.rpool, + if (pfctl_get_pool(dev, &rule.rpool, nr, pr.ticket, PF_PASS, path) != 0) goto error; switch (format) { case PFCTL_SHOW_LABELS: - if (pr.rule.label[0]) { + if (rule.label[0]) { printf("%s %llu %llu %llu %llu" " %llu %llu %llu %ju\n", - pr.rule.label, - (unsigned long long)pr.rule.evaluations, - (unsigned long long)(pr.rule.packets[0] + - pr.rule.packets[1]), - (unsigned long long)(pr.rule.bytes[0] + - pr.rule.bytes[1]), - (unsigned long long)pr.rule.packets[0], - (unsigned long long)pr.rule.bytes[0], - (unsigned long long)pr.rule.packets[1], - (unsigned long long)pr.rule.bytes[1], - (uintmax_t)pr.rule.u_states_tot); + rule.label, + (unsigned long long)rule.evaluations, + (unsigned long long)(rule.packets[0] + + rule.packets[1]), + (unsigned long long)(rule.bytes[0] + + rule.bytes[1]), + (unsigned long long)rule.packets[0], + (unsigned long long)rule.bytes[0], + (unsigned long long)rule.packets[1], + (unsigned long long)rule.bytes[1], + (uintmax_t)rule.u_states_tot); } break; case PFCTL_SHOW_RULES: brace = 0; - if (pr.rule.label[0] && (opts & PF_OPT_SHOWALL)) + if (rule.label[0] && (opts & PF_OPT_SHOWALL)) labels = 1; INDENT(depth, !(opts & PF_OPT_VERBOSE)); if (pr.anchor_call[0] && ((((p = strrchr(pr.anchor_call, '_')) != NULL) && ((void *)p == (void *)pr.anchor_call || *(--p) == '/')) || (opts & PF_OPT_RECURSE))) { brace++; if ((p = strrchr(pr.anchor_call, '/')) != NULL) p++; else p = &pr.anchor_call[0]; } else p = &pr.anchor_call[0]; - print_rule(&pr.rule, p, rule_numbers, numeric); + print_rule(&rule, p, rule_numbers, numeric); if (brace) printf(" {\n"); else printf("\n"); - pfctl_print_rule_counters(&pr.rule, opts); + pfctl_print_rule_counters(&rule, opts); if (brace) { pfctl_show_rules(dev, path, opts, format, p, depth + 1); INDENT(depth, !(opts & PF_OPT_VERBOSE)); printf("}\n"); } break; case PFCTL_SHOW_NOTHING: break; } - pfctl_clear_pool(&pr.rule.rpool); + pfctl_clear_pool(&rule.rpool); } path[len] = '\0'; return (0); error: path[len] = '\0'; return (-1); } int pfctl_show_nat(int dev, int opts, char *anchorname) { struct pfioc_rule pr; + struct pfctl_rule rule; u_int32_t mnr, nr; static int nattype[3] = { PF_NAT, PF_RDR, PF_BINAT }; int i, dotitle = opts & PF_OPT_SHOWALL; memset(&pr, 0, sizeof(pr)); memcpy(pr.anchor, anchorname, sizeof(pr.anchor)); for (i = 0; i < 3; i++) { pr.rule.action = nattype[i]; if (ioctl(dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); return (-1); } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { pr.nr = nr; if (pfctl_get_rule(dev, nr, pr.ticket, anchorname, - nattype[i], &pr.rule, pr.anchor_call)) { + nattype[i], &rule, pr.anchor_call)) { warn("DIOCGETRULE"); return (-1); } - if (pfctl_get_pool(dev, &pr.rule.rpool, nr, + if (pfctl_get_pool(dev, &rule.rpool, nr, pr.ticket, nattype[i], anchorname) != 0) return (-1); if (dotitle) { pfctl_print_title("TRANSLATION RULES:"); dotitle = 0; } - print_rule(&pr.rule, pr.anchor_call, + print_rule(&rule, pr.anchor_call, opts & PF_OPT_VERBOSE2, opts & PF_OPT_NUMERIC); printf("\n"); - pfctl_print_rule_counters(&pr.rule, opts); - pfctl_clear_pool(&pr.rule.rpool); + pfctl_print_rule_counters(&rule, opts); + pfctl_clear_pool(&rule.rpool); } } return (0); } int pfctl_show_src_nodes(int dev, int opts) { struct pfioc_src_nodes psn; struct pf_src_node *p; char *inbuf = NULL, *newinbuf = NULL; unsigned int len = 0; int i; memset(&psn, 0, sizeof(psn)); for (;;) { psn.psn_len = len; if (len) { newinbuf = realloc(inbuf, len); if (newinbuf == NULL) err(1, "realloc"); psn.psn_buf = inbuf = newinbuf; } if (ioctl(dev, DIOCGETSRCNODES, &psn) < 0) { warn("DIOCGETSRCNODES"); free(inbuf); return (-1); } if (psn.psn_len + sizeof(struct pfioc_src_nodes) < len) break; if (len == 0 && psn.psn_len == 0) goto done; if (len == 0 && psn.psn_len != 0) len = psn.psn_len; if (psn.psn_len == 0) goto done; /* no src_nodes */ len *= 2; } p = psn.psn_src_nodes; if (psn.psn_len > 0 && (opts & PF_OPT_SHOWALL)) pfctl_print_title("SOURCE TRACKING NODES:"); for (i = 0; i < psn.psn_len; i += sizeof(*p)) { print_src_node(p, opts); p++; } done: free(inbuf); return (0); } int pfctl_show_states(int dev, const char *iface, int opts) { struct pfioc_states ps; struct pfsync_state *p; char *inbuf = NULL, *newinbuf = NULL; unsigned int len = 0; int i, dotitle = (opts & PF_OPT_SHOWALL); memset(&ps, 0, sizeof(ps)); for (;;) { ps.ps_len = len; if (len) { newinbuf = realloc(inbuf, len); if (newinbuf == NULL) err(1, "realloc"); ps.ps_buf = inbuf = newinbuf; } if (ioctl(dev, DIOCGETSTATES, &ps) < 0) { warn("DIOCGETSTATES"); free(inbuf); return (-1); } if (ps.ps_len + sizeof(struct pfioc_states) < len) break; if (len == 0 && ps.ps_len == 0) goto done; if (len == 0 && ps.ps_len != 0) len = ps.ps_len; if (ps.ps_len == 0) goto done; /* no states */ len *= 2; } p = ps.ps_states; for (i = 0; i < ps.ps_len; i += sizeof(*p), p++) { if (iface != NULL && strcmp(p->ifname, iface)) continue; if (dotitle) { pfctl_print_title("STATES:"); dotitle = 0; } print_state(p, opts); } done: free(inbuf); return (0); } int pfctl_show_status(int dev, int opts) { struct pf_status status; if (ioctl(dev, DIOCGETSTATUS, &status)) { warn("DIOCGETSTATUS"); return (-1); } if (opts & PF_OPT_SHOWALL) pfctl_print_title("INFO:"); print_status(&status, opts); return (0); } int pfctl_show_running(int dev) { struct pf_status status; if (ioctl(dev, DIOCGETSTATUS, &status)) { warn("DIOCGETSTATUS"); return (-1); } print_running(&status); return (!status.running); } int pfctl_show_timeouts(int dev, int opts) { struct pfioc_tm pt; int i; if (opts & PF_OPT_SHOWALL) pfctl_print_title("TIMEOUTS:"); memset(&pt, 0, sizeof(pt)); for (i = 0; pf_timeouts[i].name; i++) { pt.timeout = pf_timeouts[i].timeout; if (ioctl(dev, DIOCGETTIMEOUT, &pt)) err(1, "DIOCGETTIMEOUT"); printf("%-20s %10d", pf_timeouts[i].name, pt.seconds); if (pf_timeouts[i].timeout >= PFTM_ADAPTIVE_START && pf_timeouts[i].timeout <= PFTM_ADAPTIVE_END) printf(" states"); else printf("s"); printf("\n"); } return (0); } int pfctl_show_limits(int dev, int opts) { struct pfioc_limit pl; int i; if (opts & PF_OPT_SHOWALL) pfctl_print_title("LIMITS:"); memset(&pl, 0, sizeof(pl)); for (i = 0; pf_limits[i].name; i++) { pl.index = pf_limits[i].index; if (ioctl(dev, DIOCGETLIMIT, &pl)) err(1, "DIOCGETLIMIT"); printf("%-13s ", pf_limits[i].name); if (pl.limit == UINT_MAX) printf("unlimited\n"); else printf("hard limit %8u\n", pl.limit); } return (0); } /* callbacks for rule/nat/rdr/addr */ int pfctl_add_pool(struct pfctl *pf, struct pf_pool *p, sa_family_t af) { struct pf_pooladdr *pa; if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCBEGINADDRS, &pf->paddr)) err(1, "DIOCBEGINADDRS"); } pf->paddr.af = af; TAILQ_FOREACH(pa, &p->list, entries) { memcpy(&pf->paddr.addr, pa, sizeof(struct pf_pooladdr)); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCADDADDR, &pf->paddr)) err(1, "DIOCADDADDR"); } } return (0); } int -pfctl_append_rule(struct pfctl *pf, struct pf_rule *r, const char *anchor_call) +pfctl_append_rule(struct pfctl *pf, struct pfctl_rule *r, + const char *anchor_call) { u_int8_t rs_num; - struct pf_rule *rule; - struct pf_ruleset *rs; + struct pfctl_rule *rule; + struct pfctl_ruleset *rs; char *p; rs_num = pf_get_ruleset_number(r->action); if (rs_num == PF_RULESET_MAX) errx(1, "Invalid rule type %d", r->action); rs = &pf->anchor->ruleset; if (anchor_call[0] && r->anchor == NULL) { /* * Don't make non-brace anchors part of the main anchor pool. */ if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL) err(1, "pfctl_append_rule: calloc"); pf_init_ruleset(&r->anchor->ruleset); r->anchor->ruleset.anchor = r->anchor; if (strlcpy(r->anchor->path, anchor_call, sizeof(rule->anchor->path)) >= sizeof(rule->anchor->path)) errx(1, "pfctl_append_rule: strlcpy"); if ((p = strrchr(anchor_call, '/')) != NULL) { if (!strlen(p)) err(1, "pfctl_append_rule: bad anchor name %s", anchor_call); } else p = (char *)anchor_call; if (strlcpy(r->anchor->name, p, sizeof(rule->anchor->name)) >= sizeof(rule->anchor->name)) errx(1, "pfctl_append_rule: strlcpy"); } if ((rule = calloc(1, sizeof(*rule))) == NULL) err(1, "calloc"); bcopy(r, rule, sizeof(*rule)); TAILQ_INIT(&rule->rpool.list); pfctl_move_pool(&r->rpool, &rule->rpool); TAILQ_INSERT_TAIL(rs->rules[rs_num].active.ptr, rule, entries); return (0); } int -pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pf_anchor *a) +pfctl_ruleset_trans(struct pfctl *pf, char *path, struct pfctl_anchor *a) { int osize = pf->trans->pfrb_size; if ((pf->loadopt & PFCTL_FLAG_NAT) != 0) { if (pfctl_add_trans(pf->trans, PF_RULESET_NAT, path) || pfctl_add_trans(pf->trans, PF_RULESET_BINAT, path) || pfctl_add_trans(pf->trans, PF_RULESET_RDR, path)) return (1); } if (a == pf->astack[0] && ((altqsupport && (pf->loadopt & PFCTL_FLAG_ALTQ) != 0))) { if (pfctl_add_trans(pf->trans, PF_RULESET_ALTQ, path)) return (2); } if ((pf->loadopt & PFCTL_FLAG_FILTER) != 0) { if (pfctl_add_trans(pf->trans, PF_RULESET_SCRUB, path) || pfctl_add_trans(pf->trans, PF_RULESET_FILTER, path)) return (3); } if (pf->loadopt & PFCTL_FLAG_TABLE) if (pfctl_add_trans(pf->trans, PF_RULESET_TABLE, path)) return (4); if (pfctl_trans(pf->dev, pf->trans, DIOCXBEGIN, osize)) return (5); return (0); } int -pfctl_load_ruleset(struct pfctl *pf, char *path, struct pf_ruleset *rs, +pfctl_load_ruleset(struct pfctl *pf, char *path, struct pfctl_ruleset *rs, int rs_num, int depth) { - struct pf_rule *r; + struct pfctl_rule *r; int error, len = strlen(path); int brace = 0; pf->anchor = rs->anchor; if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", pf->anchor->name); else snprintf(&path[len], MAXPATHLEN - len, "%s", pf->anchor->name); if (depth) { if (TAILQ_FIRST(rs->rules[rs_num].active.ptr) != NULL) { brace++; if (pf->opts & PF_OPT_VERBOSE) printf(" {\n"); if ((pf->opts & PF_OPT_NOACTION) == 0 && (error = pfctl_ruleset_trans(pf, path, rs->anchor))) { printf("pfctl_load_rulesets: " "pfctl_ruleset_trans %d\n", error); goto error; } } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); } if (pf->optimize && rs_num == PF_RULESET_FILTER) pfctl_optimize_ruleset(pf, rs); while ((r = TAILQ_FIRST(rs->rules[rs_num].active.ptr)) != NULL) { TAILQ_REMOVE(rs->rules[rs_num].active.ptr, r, entries); if ((error = pfctl_load_rule(pf, path, r, depth))) goto error; if (r->anchor) { if ((error = pfctl_load_ruleset(pf, path, &r->anchor->ruleset, rs_num, depth + 1))) goto error; } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); free(r); } if (brace && pf->opts & PF_OPT_VERBOSE) { INDENT(depth - 1, (pf->opts & PF_OPT_VERBOSE)); printf("}\n"); } path[len] = '\0'; return (0); error: path[len] = '\0'; return (error); } int -pfctl_load_rule(struct pfctl *pf, char *path, struct pf_rule *r, int depth) +pfctl_load_rule(struct pfctl *pf, char *path, struct pfctl_rule *r, int depth) { u_int8_t rs_num = pf_get_ruleset_number(r->action); char *name; u_int32_t ticket; char anchor[PF_ANCHOR_NAME_SIZE]; int len = strlen(path); /* set up anchor before adding to path for anchor_call */ if ((pf->opts & PF_OPT_NOACTION) == 0) ticket = pfctl_get_ticket(pf->trans, rs_num, path); if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor)) errx(1, "pfctl_load_rule: strlcpy"); if (r->anchor) { if (r->anchor->match) { if (path[0]) snprintf(&path[len], MAXPATHLEN - len, "/%s", r->anchor->name); else snprintf(&path[len], MAXPATHLEN - len, "%s", r->anchor->name); name = r->anchor->name; } else name = r->anchor->path; } else name = ""; if ((pf->opts & PF_OPT_NOACTION) == 0) { if (pfctl_add_pool(pf, &r->rpool, r->af)) return (1); if (pfctl_add_rule(pf->dev, r, anchor, name, ticket, pf->paddr.ticket)) err(1, "DIOCADDRULENV"); } if (pf->opts & PF_OPT_VERBOSE) { INDENT(depth, !(pf->opts & PF_OPT_VERBOSE2)); print_rule(r, r->anchor ? r->anchor->name : "", pf->opts & PF_OPT_VERBOSE2, pf->opts & PF_OPT_NUMERIC); } path[len] = '\0'; pfctl_clear_pool(&r->rpool); return (0); } int pfctl_add_altq(struct pfctl *pf, struct pf_altq *a) { if (altqsupport && (loadopt & PFCTL_FLAG_ALTQ) != 0) { memcpy(&pf->paltq->altq, a, sizeof(struct pf_altq)); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (ioctl(pf->dev, DIOCADDALTQ, pf->paltq)) { if (errno == ENXIO) errx(1, "qtype not configured"); else if (errno == ENODEV) errx(1, "%s: driver does not support " "altq", a->ifname); else err(1, "DIOCADDALTQ"); } } pfaltq_store(&pf->paltq->altq); } return (0); } int pfctl_rules(int dev, char *filename, int opts, int optimize, char *anchorname, struct pfr_buffer *trans) { #define ERR(x) do { warn(x); goto _error; } while(0) #define ERRX(x) do { warnx(x); goto _error; } while(0) struct pfr_buffer *t, buf; struct pfioc_altq pa; struct pfctl pf; - struct pf_ruleset *rs; + struct pfctl_ruleset *rs; struct pfr_table trs; char *path; int osize; RB_INIT(&pf_anchors); memset(&pf_main_anchor, 0, sizeof(pf_main_anchor)); pf_init_ruleset(&pf_main_anchor.ruleset); pf_main_anchor.ruleset.anchor = &pf_main_anchor; if (trans == NULL) { bzero(&buf, sizeof(buf)); buf.pfrb_type = PFRB_TRANS; t = &buf; osize = 0; } else { t = trans; osize = t->pfrb_size; } memset(&pa, 0, sizeof(pa)); pa.version = PFIOC_ALTQ_VERSION; memset(&pf, 0, sizeof(pf)); memset(&trs, 0, sizeof(trs)); if ((path = calloc(1, MAXPATHLEN)) == NULL) ERRX("pfctl_rules: calloc"); if (strlcpy(trs.pfrt_anchor, anchorname, sizeof(trs.pfrt_anchor)) >= sizeof(trs.pfrt_anchor)) ERRX("pfctl_rules: strlcpy"); pf.dev = dev; pf.opts = opts; pf.optimize = optimize; pf.loadopt = loadopt; /* non-brace anchor, create without resolving the path */ if ((pf.anchor = calloc(1, sizeof(*pf.anchor))) == NULL) ERRX("pfctl_rules: calloc"); rs = &pf.anchor->ruleset; pf_init_ruleset(rs); rs->anchor = pf.anchor; if (strlcpy(pf.anchor->path, anchorname, sizeof(pf.anchor->path)) >= sizeof(pf.anchor->path)) errx(1, "pfctl_add_rule: strlcpy"); if (strlcpy(pf.anchor->name, anchorname, sizeof(pf.anchor->name)) >= sizeof(pf.anchor->name)) errx(1, "pfctl_add_rule: strlcpy"); pf.astack[0] = pf.anchor; pf.asd = 0; if (anchorname[0]) pf.loadopt &= ~PFCTL_FLAG_ALTQ; pf.paltq = &pa; pf.trans = t; pfctl_init_options(&pf); if ((opts & PF_OPT_NOACTION) == 0) { /* * XXX For the time being we need to open transactions for * the main ruleset before parsing, because tables are still * loaded at parse time. */ if (pfctl_ruleset_trans(&pf, anchorname, pf.anchor)) ERRX("pfctl_rules"); if (altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ)) pa.ticket = pfctl_get_ticket(t, PF_RULESET_ALTQ, anchorname); if (pf.loadopt & PFCTL_FLAG_TABLE) pf.astack[0]->ruleset.tticket = pfctl_get_ticket(t, PF_RULESET_TABLE, anchorname); } if (parse_config(filename, &pf) < 0) { if ((opts & PF_OPT_NOACTION) == 0) ERRX("Syntax error in config file: " "pf rules not loaded"); else goto _error; } if (loadopt & PFCTL_FLAG_OPTION) pfctl_adjust_skip_ifaces(&pf); if ((pf.loadopt & PFCTL_FLAG_FILTER && (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_SCRUB, 0))) || (pf.loadopt & PFCTL_FLAG_NAT && (pfctl_load_ruleset(&pf, path, rs, PF_RULESET_NAT, 0) || pfctl_load_ruleset(&pf, path, rs, PF_RULESET_RDR, 0) || pfctl_load_ruleset(&pf, path, rs, PF_RULESET_BINAT, 0))) || (pf.loadopt & PFCTL_FLAG_FILTER && pfctl_load_ruleset(&pf, path, rs, PF_RULESET_FILTER, 0))) { if ((opts & PF_OPT_NOACTION) == 0) ERRX("Unable to load rules into kernel"); else goto _error; } if ((altqsupport && (pf.loadopt & PFCTL_FLAG_ALTQ) != 0)) if (check_commit_altq(dev, opts) != 0) ERRX("errors in altq config"); /* process "load anchor" directives */ if (!anchorname[0]) if (pfctl_load_anchors(dev, &pf, t) == -1) ERRX("load anchors"); if (trans == NULL && (opts & PF_OPT_NOACTION) == 0) { if (!anchorname[0]) if (pfctl_load_options(&pf)) goto _error; if (pfctl_trans(dev, t, DIOCXCOMMIT, osize)) ERR("DIOCXCOMMIT"); } free(path); return (0); _error: if (trans == NULL) { /* main ruleset */ if ((opts & PF_OPT_NOACTION) == 0) if (pfctl_trans(dev, t, DIOCXROLLBACK, osize)) err(1, "DIOCXROLLBACK"); exit(1); } else { /* sub ruleset */ free(path); return (-1); } #undef ERR #undef ERRX } FILE * pfctl_fopen(const char *name, const char *mode) { struct stat st; FILE *fp; fp = fopen(name, mode); if (fp == NULL) return (NULL); if (fstat(fileno(fp), &st)) { fclose(fp); return (NULL); } if (S_ISDIR(st.st_mode)) { fclose(fp); errno = EISDIR; return (NULL); } return (fp); } void pfctl_init_options(struct pfctl *pf) { pf->timeout[PFTM_TCP_FIRST_PACKET] = PFTM_TCP_FIRST_PACKET_VAL; pf->timeout[PFTM_TCP_OPENING] = PFTM_TCP_OPENING_VAL; pf->timeout[PFTM_TCP_ESTABLISHED] = PFTM_TCP_ESTABLISHED_VAL; pf->timeout[PFTM_TCP_CLOSING] = PFTM_TCP_CLOSING_VAL; pf->timeout[PFTM_TCP_FIN_WAIT] = PFTM_TCP_FIN_WAIT_VAL; pf->timeout[PFTM_TCP_CLOSED] = PFTM_TCP_CLOSED_VAL; pf->timeout[PFTM_UDP_FIRST_PACKET] = PFTM_UDP_FIRST_PACKET_VAL; pf->timeout[PFTM_UDP_SINGLE] = PFTM_UDP_SINGLE_VAL; pf->timeout[PFTM_UDP_MULTIPLE] = PFTM_UDP_MULTIPLE_VAL; pf->timeout[PFTM_ICMP_FIRST_PACKET] = PFTM_ICMP_FIRST_PACKET_VAL; pf->timeout[PFTM_ICMP_ERROR_REPLY] = PFTM_ICMP_ERROR_REPLY_VAL; pf->timeout[PFTM_OTHER_FIRST_PACKET] = PFTM_OTHER_FIRST_PACKET_VAL; pf->timeout[PFTM_OTHER_SINGLE] = PFTM_OTHER_SINGLE_VAL; pf->timeout[PFTM_OTHER_MULTIPLE] = PFTM_OTHER_MULTIPLE_VAL; pf->timeout[PFTM_FRAG] = PFTM_FRAG_VAL; pf->timeout[PFTM_INTERVAL] = PFTM_INTERVAL_VAL; pf->timeout[PFTM_SRC_NODE] = PFTM_SRC_NODE_VAL; pf->timeout[PFTM_TS_DIFF] = PFTM_TS_DIFF_VAL; pf->timeout[PFTM_ADAPTIVE_START] = PFSTATE_ADAPT_START; pf->timeout[PFTM_ADAPTIVE_END] = PFSTATE_ADAPT_END; pf->limit[PF_LIMIT_STATES] = PFSTATE_HIWAT; pf->limit[PF_LIMIT_FRAGS] = PFFRAG_FRENT_HIWAT; pf->limit[PF_LIMIT_SRC_NODES] = PFSNODE_HIWAT; pf->limit[PF_LIMIT_TABLE_ENTRIES] = PFR_KENTRY_HIWAT; pf->debug = PF_DEBUG_URGENT; } int pfctl_load_options(struct pfctl *pf) { int i, error = 0; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); /* load limits */ for (i = 0; i < PF_LIMIT_MAX; i++) { if ((pf->opts & PF_OPT_MERGE) && !pf->limit_set[i]) continue; if (pfctl_load_limit(pf, i, pf->limit[i])) error = 1; } /* * If we've set the limit, but haven't explicitly set adaptive * timeouts, do it now with a start of 60% and end of 120%. */ if (pf->limit_set[PF_LIMIT_STATES] && !pf->timeout_set[PFTM_ADAPTIVE_START] && !pf->timeout_set[PFTM_ADAPTIVE_END]) { pf->timeout[PFTM_ADAPTIVE_START] = (pf->limit[PF_LIMIT_STATES] / 10) * 6; pf->timeout_set[PFTM_ADAPTIVE_START] = 1; pf->timeout[PFTM_ADAPTIVE_END] = (pf->limit[PF_LIMIT_STATES] / 10) * 12; pf->timeout_set[PFTM_ADAPTIVE_END] = 1; } /* load timeouts */ for (i = 0; i < PFTM_MAX; i++) { if ((pf->opts & PF_OPT_MERGE) && !pf->timeout_set[i]) continue; if (pfctl_load_timeout(pf, i, pf->timeout[i])) error = 1; } /* load debug */ if (!(pf->opts & PF_OPT_MERGE) || pf->debug_set) if (pfctl_load_debug(pf, pf->debug)) error = 1; /* load logif */ if (!(pf->opts & PF_OPT_MERGE) || pf->ifname_set) if (pfctl_load_logif(pf, pf->ifname)) error = 1; /* load hostid */ if (!(pf->opts & PF_OPT_MERGE) || pf->hostid_set) if (pfctl_load_hostid(pf, pf->hostid)) error = 1; return (error); } int pfctl_set_limit(struct pfctl *pf, const char *opt, unsigned int limit) { int i; for (i = 0; pf_limits[i].name; i++) { if (strcasecmp(opt, pf_limits[i].name) == 0) { pf->limit[pf_limits[i].index] = limit; pf->limit_set[pf_limits[i].index] = 1; break; } } if (pf_limits[i].name == NULL) { warnx("Bad pool name."); return (1); } if (pf->opts & PF_OPT_VERBOSE) printf("set limit %s %d\n", opt, limit); return (0); } int pfctl_load_limit(struct pfctl *pf, unsigned int index, unsigned int limit) { struct pfioc_limit pl; memset(&pl, 0, sizeof(pl)); pl.index = index; pl.limit = limit; if (ioctl(pf->dev, DIOCSETLIMIT, &pl)) { if (errno == EBUSY) warnx("Current pool size exceeds requested hard limit"); else warnx("DIOCSETLIMIT"); return (1); } return (0); } int pfctl_set_timeout(struct pfctl *pf, const char *opt, int seconds, int quiet) { int i; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); for (i = 0; pf_timeouts[i].name; i++) { if (strcasecmp(opt, pf_timeouts[i].name) == 0) { pf->timeout[pf_timeouts[i].timeout] = seconds; pf->timeout_set[pf_timeouts[i].timeout] = 1; break; } } if (pf_timeouts[i].name == NULL) { warnx("Bad timeout name."); return (1); } if (pf->opts & PF_OPT_VERBOSE && ! quiet) printf("set timeout %s %d\n", opt, seconds); return (0); } int pfctl_load_timeout(struct pfctl *pf, unsigned int timeout, unsigned int seconds) { struct pfioc_tm pt; memset(&pt, 0, sizeof(pt)); pt.timeout = timeout; pt.seconds = seconds; if (ioctl(pf->dev, DIOCSETTIMEOUT, &pt)) { warnx("DIOCSETTIMEOUT"); return (1); } return (0); } int pfctl_set_optimization(struct pfctl *pf, const char *opt) { const struct pf_hint *hint; int i, r; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); for (i = 0; pf_hints[i].name; i++) if (strcasecmp(opt, pf_hints[i].name) == 0) break; hint = pf_hints[i].hint; if (hint == NULL) { warnx("invalid state timeouts optimization"); return (1); } for (i = 0; hint[i].name; i++) if ((r = pfctl_set_timeout(pf, hint[i].name, hint[i].timeout, 1))) return (r); if (pf->opts & PF_OPT_VERBOSE) printf("set optimization %s\n", opt); return (0); } int pfctl_set_logif(struct pfctl *pf, char *ifname) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); if (!strcmp(ifname, "none")) { free(pf->ifname); pf->ifname = NULL; } else { pf->ifname = strdup(ifname); if (!pf->ifname) errx(1, "pfctl_set_logif: strdup"); } pf->ifname_set = 1; if (pf->opts & PF_OPT_VERBOSE) printf("set loginterface %s\n", ifname); return (0); } int pfctl_load_logif(struct pfctl *pf, char *ifname) { struct pfioc_if pi; memset(&pi, 0, sizeof(pi)); if (ifname && strlcpy(pi.ifname, ifname, sizeof(pi.ifname)) >= sizeof(pi.ifname)) { warnx("pfctl_load_logif: strlcpy"); return (1); } if (ioctl(pf->dev, DIOCSETSTATUSIF, &pi)) { warnx("DIOCSETSTATUSIF"); return (1); } return (0); } int pfctl_set_hostid(struct pfctl *pf, u_int32_t hostid) { if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); HTONL(hostid); pf->hostid = hostid; pf->hostid_set = 1; if (pf->opts & PF_OPT_VERBOSE) printf("set hostid 0x%08x\n", ntohl(hostid)); return (0); } int pfctl_load_hostid(struct pfctl *pf, u_int32_t hostid) { if (ioctl(dev, DIOCSETHOSTID, &hostid)) { warnx("DIOCSETHOSTID"); return (1); } return (0); } int pfctl_set_debug(struct pfctl *pf, char *d) { u_int32_t level; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); if (!strcmp(d, "none")) pf->debug = PF_DEBUG_NONE; else if (!strcmp(d, "urgent")) pf->debug = PF_DEBUG_URGENT; else if (!strcmp(d, "misc")) pf->debug = PF_DEBUG_MISC; else if (!strcmp(d, "loud")) pf->debug = PF_DEBUG_NOISY; else { warnx("unknown debug level \"%s\"", d); return (-1); } pf->debug_set = 1; level = pf->debug; if ((pf->opts & PF_OPT_NOACTION) == 0) if (ioctl(dev, DIOCSETDEBUG, &level)) err(1, "DIOCSETDEBUG"); if (pf->opts & PF_OPT_VERBOSE) printf("set debug %s\n", d); return (0); } int pfctl_load_debug(struct pfctl *pf, unsigned int level) { if (ioctl(pf->dev, DIOCSETDEBUG, &level)) { warnx("DIOCSETDEBUG"); return (1); } return (0); } int pfctl_set_interface_flags(struct pfctl *pf, char *ifname, int flags, int how) { struct pfioc_iface pi; struct node_host *h = NULL, *n = NULL; if ((loadopt & PFCTL_FLAG_OPTION) == 0) return (0); bzero(&pi, sizeof(pi)); pi.pfiio_flags = flags; /* Make sure our cache matches the kernel. If we set or clear the flag * for a group this applies to all members. */ h = ifa_grouplookup(ifname, 0); for (n = h; n != NULL; n = n->next) pfctl_set_interface_flags(pf, n->ifname, flags, how); if (strlcpy(pi.pfiio_name, ifname, sizeof(pi.pfiio_name)) >= sizeof(pi.pfiio_name)) errx(1, "pfctl_set_interface_flags: strlcpy"); if ((pf->opts & PF_OPT_NOACTION) == 0) { if (how == 0) { if (ioctl(pf->dev, DIOCCLRIFFLAG, &pi)) err(1, "DIOCCLRIFFLAG"); } else { if (ioctl(pf->dev, DIOCSETIFFLAG, &pi)) err(1, "DIOCSETIFFLAG"); pfctl_check_skip_ifaces(ifname); } } return (0); } void pfctl_debug(int dev, u_int32_t level, int opts) { if (ioctl(dev, DIOCSETDEBUG, &level)) err(1, "DIOCSETDEBUG"); if ((opts & PF_OPT_QUIET) == 0) { fprintf(stderr, "debug level set to '"); switch (level) { case PF_DEBUG_NONE: fprintf(stderr, "none"); break; case PF_DEBUG_URGENT: fprintf(stderr, "urgent"); break; case PF_DEBUG_MISC: fprintf(stderr, "misc"); break; case PF_DEBUG_NOISY: fprintf(stderr, "loud"); break; default: fprintf(stderr, ""); break; } fprintf(stderr, "'\n"); } } int pfctl_test_altqsupport(int dev, int opts) { struct pfioc_altq pa; pa.version = PFIOC_ALTQ_VERSION; if (ioctl(dev, DIOCGETALTQS, &pa)) { if (errno == ENODEV) { if (opts & PF_OPT_VERBOSE) fprintf(stderr, "No ALTQ support in kernel\n" "ALTQ related functions disabled\n"); return (0); } else err(1, "DIOCGETALTQS"); } return (1); } int pfctl_show_anchors(int dev, int opts, char *anchorname) { struct pfioc_ruleset pr; u_int32_t mnr, nr; memset(&pr, 0, sizeof(pr)); memcpy(pr.path, anchorname, sizeof(pr.path)); if (ioctl(dev, DIOCGETRULESETS, &pr)) { if (errno == EINVAL) fprintf(stderr, "Anchor '%s' not found.\n", anchorname); else err(1, "DIOCGETRULESETS"); return (-1); } mnr = pr.nr; for (nr = 0; nr < mnr; ++nr) { char sub[MAXPATHLEN]; pr.nr = nr; if (ioctl(dev, DIOCGETRULESET, &pr)) err(1, "DIOCGETRULESET"); if (!strcmp(pr.name, PF_RESERVED_ANCHOR)) continue; sub[0] = 0; if (pr.path[0]) { strlcat(sub, pr.path, sizeof(sub)); strlcat(sub, "/", sizeof(sub)); } strlcat(sub, pr.name, sizeof(sub)); if (sub[0] != '_' || (opts & PF_OPT_VERBOSE)) printf(" %s\n", sub); if ((opts & PF_OPT_VERBOSE) && pfctl_show_anchors(dev, opts, sub)) return (-1); } return (0); } const char * pfctl_lookup_option(char *cmd, const char * const *list) { if (cmd != NULL && *cmd) for (; *list; list++) if (!strncmp(cmd, *list, strlen(cmd))) return (*list); return (NULL); } int main(int argc, char *argv[]) { int error = 0; int ch; int mode = O_RDONLY; int opts = 0; int optimize = PF_OPTIMIZE_BASIC; char anchorname[MAXPATHLEN]; char *path; if (argc < 2) usage(); while ((ch = getopt(argc, argv, "a:AdD:eqf:F:ghi:k:K:mnNOo:Pp:rRs:t:T:vx:z")) != -1) { switch (ch) { case 'a': anchoropt = optarg; break; case 'd': opts |= PF_OPT_DISABLE; mode = O_RDWR; break; case 'D': if (pfctl_cmdline_symset(optarg) < 0) warnx("could not parse macro definition %s", optarg); break; case 'e': opts |= PF_OPT_ENABLE; mode = O_RDWR; break; case 'q': opts |= PF_OPT_QUIET; break; case 'F': clearopt = pfctl_lookup_option(optarg, clearopt_list); if (clearopt == NULL) { warnx("Unknown flush modifier '%s'", optarg); usage(); } mode = O_RDWR; break; case 'i': ifaceopt = optarg; break; case 'k': if (state_killers >= 2) { warnx("can only specify -k twice"); usage(); /* NOTREACHED */ } state_kill[state_killers++] = optarg; mode = O_RDWR; break; case 'K': if (src_node_killers >= 2) { warnx("can only specify -K twice"); usage(); /* NOTREACHED */ } src_node_kill[src_node_killers++] = optarg; mode = O_RDWR; break; case 'm': opts |= PF_OPT_MERGE; break; case 'n': opts |= PF_OPT_NOACTION; break; case 'N': loadopt |= PFCTL_FLAG_NAT; break; case 'r': opts |= PF_OPT_USEDNS; break; case 'f': rulesopt = optarg; mode = O_RDWR; break; case 'g': opts |= PF_OPT_DEBUG; break; case 'A': loadopt |= PFCTL_FLAG_ALTQ; break; case 'R': loadopt |= PFCTL_FLAG_FILTER; break; case 'o': optiopt = pfctl_lookup_option(optarg, optiopt_list); if (optiopt == NULL) { warnx("Unknown optimization '%s'", optarg); usage(); } opts |= PF_OPT_OPTIMIZE; break; case 'O': loadopt |= PFCTL_FLAG_OPTION; break; case 'p': pf_device = optarg; break; case 'P': opts |= PF_OPT_NUMERIC; break; case 's': showopt = pfctl_lookup_option(optarg, showopt_list); if (showopt == NULL) { warnx("Unknown show modifier '%s'", optarg); usage(); } break; case 't': tableopt = optarg; break; case 'T': tblcmdopt = pfctl_lookup_option(optarg, tblcmdopt_list); if (tblcmdopt == NULL) { warnx("Unknown table command '%s'", optarg); usage(); } break; case 'v': if (opts & PF_OPT_VERBOSE) opts |= PF_OPT_VERBOSE2; opts |= PF_OPT_VERBOSE; break; case 'x': debugopt = pfctl_lookup_option(optarg, debugopt_list); if (debugopt == NULL) { warnx("Unknown debug level '%s'", optarg); usage(); } mode = O_RDWR; break; case 'z': opts |= PF_OPT_CLRRULECTRS; mode = O_RDWR; break; case 'h': /* FALLTHROUGH */ default: usage(); /* NOTREACHED */ } } if (tblcmdopt != NULL) { argc -= optind; argv += optind; ch = *tblcmdopt; if (ch == 'l') { loadopt |= PFCTL_FLAG_TABLE; tblcmdopt = NULL; } else mode = strchr("acdefkrz", ch) ? O_RDWR : O_RDONLY; } else if (argc != optind) { warnx("unknown command line argument: %s ...", argv[optind]); usage(); /* NOTREACHED */ } if (loadopt == 0) loadopt = ~0; if ((path = calloc(1, MAXPATHLEN)) == NULL) errx(1, "pfctl: calloc"); memset(anchorname, 0, sizeof(anchorname)); if (anchoropt != NULL) { int len = strlen(anchoropt); if (anchoropt[len - 1] == '*') { if (len >= 2 && anchoropt[len - 2] == '/') anchoropt[len - 2] = '\0'; else anchoropt[len - 1] = '\0'; opts |= PF_OPT_RECURSE; } if (strlcpy(anchorname, anchoropt, sizeof(anchorname)) >= sizeof(anchorname)) errx(1, "anchor name '%s' too long", anchoropt); loadopt &= PFCTL_FLAG_FILTER|PFCTL_FLAG_NAT|PFCTL_FLAG_TABLE; } if ((opts & PF_OPT_NOACTION) == 0) { dev = open(pf_device, mode); if (dev == -1) err(1, "%s", pf_device); altqsupport = pfctl_test_altqsupport(dev, opts); } else { dev = open(pf_device, O_RDONLY); if (dev >= 0) opts |= PF_OPT_DUMMYACTION; /* turn off options */ opts &= ~ (PF_OPT_DISABLE | PF_OPT_ENABLE); clearopt = showopt = debugopt = NULL; #if !defined(ENABLE_ALTQ) altqsupport = 0; #else altqsupport = 1; #endif } if (opts & PF_OPT_DISABLE) if (pfctl_disable(dev, opts)) error = 1; if (showopt != NULL) { switch (*showopt) { case 'A': pfctl_show_anchors(dev, opts, anchorname); break; case 'r': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_RULES, anchorname, 0); break; case 'l': pfctl_load_fingerprints(dev, opts); pfctl_show_rules(dev, path, opts, PFCTL_SHOW_LABELS, anchorname, 0); break; case 'n': pfctl_load_fingerprints(dev, opts); pfctl_show_nat(dev, opts, anchorname); break; case 'q': pfctl_show_altq(dev, ifaceopt, opts, opts & PF_OPT_VERBOSE2); break; case 's': pfctl_show_states(dev, ifaceopt, opts); break; case 'S': pfctl_show_src_nodes(dev, opts); break; case 'i': pfctl_show_status(dev, opts); break; case 'R': error = pfctl_show_running(dev); break; case 't': pfctl_show_timeouts(dev, opts); break; case 'm': pfctl_show_limits(dev, opts); break; case 'a': opts |= PF_OPT_SHOWALL; pfctl_load_fingerprints(dev, opts); pfctl_show_nat(dev, opts, anchorname); pfctl_show_rules(dev, path, opts, 0, anchorname, 0); pfctl_show_altq(dev, ifaceopt, opts, 0); pfctl_show_states(dev, ifaceopt, opts); pfctl_show_src_nodes(dev, opts); pfctl_show_status(dev, opts); pfctl_show_rules(dev, path, opts, 1, anchorname, 0); pfctl_show_timeouts(dev, opts); pfctl_show_limits(dev, opts); pfctl_show_tables(anchorname, opts); pfctl_show_fingerprints(opts); break; case 'T': pfctl_show_tables(anchorname, opts); break; case 'o': pfctl_load_fingerprints(dev, opts); pfctl_show_fingerprints(opts); break; case 'I': pfctl_show_ifaces(ifaceopt, opts); break; } } if ((opts & PF_OPT_CLRRULECTRS) && showopt == NULL) pfctl_show_rules(dev, path, opts, PFCTL_SHOW_NOTHING, anchorname, 0); if (clearopt != NULL) { if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL) errx(1, "anchor names beginning with '_' cannot " "be modified from the command line"); switch (*clearopt) { case 'r': pfctl_clear_rules(dev, opts, anchorname); break; case 'n': pfctl_clear_nat(dev, opts, anchorname); break; case 'q': pfctl_clear_altq(dev, opts); break; case 's': pfctl_clear_states(dev, ifaceopt, opts); break; case 'S': pfctl_clear_src_nodes(dev, opts); break; case 'i': pfctl_clear_stats(dev, opts); break; case 'a': pfctl_clear_rules(dev, opts, anchorname); pfctl_clear_nat(dev, opts, anchorname); pfctl_clear_tables(anchorname, opts); if (!*anchorname) { pfctl_clear_altq(dev, opts); pfctl_clear_states(dev, ifaceopt, opts); pfctl_clear_src_nodes(dev, opts); pfctl_clear_stats(dev, opts); pfctl_clear_fingerprints(dev, opts); pfctl_clear_interface_flags(dev, opts); } break; case 'o': pfctl_clear_fingerprints(dev, opts); break; case 'T': pfctl_clear_tables(anchorname, opts); break; } } if (state_killers) { if (!strcmp(state_kill[0], "label")) pfctl_label_kill_states(dev, ifaceopt, opts); else if (!strcmp(state_kill[0], "id")) pfctl_id_kill_states(dev, ifaceopt, opts); else pfctl_net_kill_states(dev, ifaceopt, opts); } if (src_node_killers) pfctl_kill_src_nodes(dev, ifaceopt, opts); if (tblcmdopt != NULL) { error = pfctl_command_tables(argc, argv, tableopt, tblcmdopt, rulesopt, anchorname, opts); rulesopt = NULL; } if (optiopt != NULL) { switch (*optiopt) { case 'n': optimize = 0; break; case 'b': optimize |= PF_OPTIMIZE_BASIC; break; case 'o': case 'p': optimize |= PF_OPTIMIZE_PROFILE; break; } } if ((rulesopt != NULL) && (loadopt & PFCTL_FLAG_OPTION) && !anchorname[0] && !(opts & PF_OPT_NOACTION)) if (pfctl_get_skip_ifaces()) error = 1; if (rulesopt != NULL && !(opts & (PF_OPT_MERGE|PF_OPT_NOACTION)) && !anchorname[0] && (loadopt & PFCTL_FLAG_OPTION)) if (pfctl_file_fingerprints(dev, opts, PF_OSFP_FILE)) error = 1; if (rulesopt != NULL) { if (anchorname[0] == '_' || strstr(anchorname, "/_") != NULL) errx(1, "anchor names beginning with '_' cannot " "be modified from the command line"); if (pfctl_rules(dev, rulesopt, opts, optimize, anchorname, NULL)) error = 1; else if (!(opts & PF_OPT_NOACTION) && (loadopt & PFCTL_FLAG_TABLE)) warn_namespace_collision(NULL); } if (opts & PF_OPT_ENABLE) if (pfctl_enable(dev, opts)) error = 1; if (debugopt != NULL) { switch (*debugopt) { case 'n': pfctl_debug(dev, PF_DEBUG_NONE, opts); break; case 'u': pfctl_debug(dev, PF_DEBUG_URGENT, opts); break; case 'm': pfctl_debug(dev, PF_DEBUG_MISC, opts); break; case 'l': pfctl_debug(dev, PF_DEBUG_NOISY, opts); break; } } exit(error); } diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h index 5aa94b2bb0c8..93b3af083b5f 100644 --- a/sbin/pfctl/pfctl.h +++ b/sbin/pfctl/pfctl.h @@ -1,139 +1,141 @@ /* $OpenBSD: pfctl.h,v 1.42 2007/12/05 12:01:47 chl Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _PFCTL_H_ #define _PFCTL_H_ +#include + enum pfctl_show { PFCTL_SHOW_RULES, PFCTL_SHOW_LABELS, PFCTL_SHOW_NOTHING }; enum { PFRB_TABLES = 1, PFRB_TSTATS, PFRB_ADDRS, PFRB_ASTATS, PFRB_IFACES, PFRB_TRANS, PFRB_MAX }; struct pfr_buffer { int pfrb_type; /* type of content, see enum above */ int pfrb_size; /* number of objects in buffer */ int pfrb_msize; /* maximum number of objects in buffer */ void *pfrb_caddr; /* malloc'ated memory area */ }; #define PFRB_FOREACH(var, buf) \ for ((var) = pfr_buf_next((buf), NULL); \ (var) != NULL; \ (var) = pfr_buf_next((buf), (var))) int pfr_get_fd(void); int pfr_clr_tables(struct pfr_table *, int *, int); int pfr_add_tables(struct pfr_table *, int, int *, int); int pfr_del_tables(struct pfr_table *, int, int *, int); int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int); int pfr_get_tstats(struct pfr_table *, struct pfr_tstats *, int *, int); int pfr_clr_tstats(struct pfr_table *, int, int *, int); int pfr_clr_addrs(struct pfr_table *, int *, int); int pfr_add_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_del_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_set_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int *, int *, int *, int); int pfr_get_addrs(struct pfr_table *, struct pfr_addr *, int *, int); int pfr_get_astats(struct pfr_table *, struct pfr_astats *, int *, int); int pfr_tst_addrs(struct pfr_table *, struct pfr_addr *, int, int *, int); int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *, int *, int, int); void pfr_buf_clear(struct pfr_buffer *); int pfr_buf_add(struct pfr_buffer *, const void *); void *pfr_buf_next(struct pfr_buffer *, const void *); int pfr_buf_grow(struct pfr_buffer *, int); int pfr_buf_load(struct pfr_buffer *, char *, int, int (*)(struct pfr_buffer *, char *, int)); char *pfr_strerror(int); int pfi_get_ifaces(const char *, struct pfi_kif *, int *); int pfi_clr_istats(const char *, int *, int); void pfctl_print_title(char *); int pfctl_clear_tables(const char *, int); int pfctl_show_tables(const char *, int); int pfctl_command_tables(int, char *[], char *, const char *, char *, const char *, int); int pfctl_show_altq(int, const char *, int, int); void warn_namespace_collision(const char *); int pfctl_show_ifaces(const char *, int); FILE *pfctl_fopen(const char *, const char *); #ifdef __FreeBSD__ extern int altqsupport; extern int dummynetsupport; #define HTONL(x) (x) = htonl((__uint32_t)(x)) #endif #ifndef DEFAULT_PRIORITY #define DEFAULT_PRIORITY 1 #endif #ifndef DEFAULT_QLIMIT #define DEFAULT_QLIMIT 50 #endif /* * generalized service curve used for admission control */ struct segment { LIST_ENTRY(segment) _next; double x, y, d, m; }; extern int loadopt; int check_commit_altq(int, int); void pfaltq_store(struct pf_altq *); char *rate2str(double); void print_addr(struct pf_addr_wrap *, sa_family_t, int); void print_host(struct pf_addr *, u_int16_t p, sa_family_t, int); void print_seq(struct pfsync_state_peer *); void print_state(struct pfsync_state *, int); int unmask(struct pf_addr *, sa_family_t); int pfctl_cmdline_symset(char *); int pfctl_add_trans(struct pfr_buffer *, int, const char *); u_int32_t pfctl_get_ticket(struct pfr_buffer *, int, const char *); int pfctl_trans(int, struct pfr_buffer *, u_long, int); int pf_get_ruleset_number(u_int8_t); -void pf_init_ruleset(struct pf_ruleset *); -int pf_anchor_setup(struct pf_rule *, - const struct pf_ruleset *, const char *); -void pf_remove_if_empty_ruleset(struct pf_ruleset *); -struct pf_ruleset *pf_find_ruleset(const char *); -struct pf_ruleset *pf_find_or_create_ruleset(const char *); +void pf_init_ruleset(struct pfctl_ruleset *); +int pfctl_anchor_setup(struct pfctl_rule *, + const struct pfctl_ruleset *, const char *); +void pf_remove_if_empty_ruleset(struct pfctl_ruleset *); +struct pfctl_ruleset *pf_find_ruleset(const char *); +struct pfctl_ruleset *pf_find_or_create_ruleset(const char *); #endif /* _PFCTL_H_ */ diff --git a/sbin/pfctl/pfctl_optimize.c b/sbin/pfctl/pfctl_optimize.c index 821a528932f3..cb5578840672 100644 --- a/sbin/pfctl/pfctl_optimize.c +++ b/sbin/pfctl/pfctl_optimize.c @@ -1,1669 +1,1669 @@ /* $OpenBSD: pfctl_optimize.c,v 1.17 2008/05/06 03:45:21 mpf Exp $ */ /* * Copyright (c) 2004 Mike Frantzen * * 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 __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" /* The size at which a table becomes faster than individual rules */ #define TABLE_THRESHOLD 6 /* #define OPT_DEBUG 1 */ #ifdef OPT_DEBUG # define DEBUG(str, v...) \ printf("%s: " str "\n", __FUNCTION__ , ## v) #else # define DEBUG(str, v...) ((void)0) #endif /* * A container that lets us sort a superblock to optimize the skip step jumps */ struct pf_skip_step { int ps_count; /* number of items */ TAILQ_HEAD( , pf_opt_rule) ps_rules; TAILQ_ENTRY(pf_skip_step) ps_entry; }; /* * A superblock is a block of adjacent rules of similar action. If there * are five PASS rules in a row, they all become members of a superblock. * Once we have a superblock, we are free to re-order any rules within it * in order to improve performance; if a packet is passed, it doesn't matter * who passed it. */ struct superblock { TAILQ_HEAD( , pf_opt_rule) sb_rules; TAILQ_ENTRY(superblock) sb_entry; struct superblock *sb_profiled_block; TAILQ_HEAD(skiplist, pf_skip_step) sb_skipsteps[PF_SKIP_COUNT]; }; TAILQ_HEAD(superblocks, superblock); /* * Description of the PF rule structure. */ enum { BARRIER, /* the presence of the field puts the rule in its own block */ BREAK, /* the field may not differ between rules in a superblock */ NOMERGE, /* the field may not differ between rules when combined */ COMBINED, /* the field may itself be combined with other rules */ DC, /* we just don't care about the field */ NEVER}; /* we should never see this field set?!? */ static struct pf_rule_field { const char *prf_name; int prf_type; size_t prf_offset; size_t prf_size; } pf_rule_desc[] = { #define PF_RULE_FIELD(field, ty) \ {#field, \ ty, \ - offsetof(struct pf_rule, field), \ - sizeof(((struct pf_rule *)0)->field)} + offsetof(struct pfctl_rule, field), \ + sizeof(((struct pfctl_rule *)0)->field)} /* * The presence of these fields in a rule put the rule in its own * superblock. Thus it will not be optimized. It also prevents the * rule from being re-ordered at all. */ PF_RULE_FIELD(label, BARRIER), PF_RULE_FIELD(prob, BARRIER), PF_RULE_FIELD(max_states, BARRIER), PF_RULE_FIELD(max_src_nodes, BARRIER), PF_RULE_FIELD(max_src_states, BARRIER), PF_RULE_FIELD(max_src_conn, BARRIER), PF_RULE_FIELD(max_src_conn_rate, BARRIER), PF_RULE_FIELD(anchor, BARRIER), /* for now */ /* * These fields must be the same between all rules in the same superblock. * These rules are allowed to be re-ordered but only among like rules. * For instance we can re-order all 'tag "foo"' rules because they have the * same tag. But we can not re-order between a 'tag "foo"' and a * 'tag "bar"' since that would change the meaning of the ruleset. */ PF_RULE_FIELD(tagname, BREAK), PF_RULE_FIELD(keep_state, BREAK), PF_RULE_FIELD(qname, BREAK), PF_RULE_FIELD(pqname, BREAK), PF_RULE_FIELD(rt, BREAK), PF_RULE_FIELD(allow_opts, BREAK), PF_RULE_FIELD(rule_flag, BREAK), PF_RULE_FIELD(action, BREAK), PF_RULE_FIELD(log, BREAK), PF_RULE_FIELD(quick, BREAK), PF_RULE_FIELD(return_ttl, BREAK), PF_RULE_FIELD(overload_tblname, BREAK), PF_RULE_FIELD(flush, BREAK), PF_RULE_FIELD(rpool, BREAK), PF_RULE_FIELD(logif, BREAK), /* * Any fields not listed in this structure act as BREAK fields */ /* * These fields must not differ when we merge two rules together but * their difference isn't enough to put the rules in different superblocks. * There are no problems re-ordering any rules with these fields. */ PF_RULE_FIELD(af, NOMERGE), PF_RULE_FIELD(ifnot, NOMERGE), PF_RULE_FIELD(ifname, NOMERGE), /* hack for IF groups */ PF_RULE_FIELD(match_tag_not, NOMERGE), PF_RULE_FIELD(match_tagname, NOMERGE), PF_RULE_FIELD(os_fingerprint, NOMERGE), PF_RULE_FIELD(timeout, NOMERGE), PF_RULE_FIELD(return_icmp, NOMERGE), PF_RULE_FIELD(return_icmp6, NOMERGE), PF_RULE_FIELD(uid, NOMERGE), PF_RULE_FIELD(gid, NOMERGE), PF_RULE_FIELD(direction, NOMERGE), PF_RULE_FIELD(proto, NOMERGE), PF_RULE_FIELD(type, NOMERGE), PF_RULE_FIELD(code, NOMERGE), PF_RULE_FIELD(flags, NOMERGE), PF_RULE_FIELD(flagset, NOMERGE), PF_RULE_FIELD(tos, NOMERGE), PF_RULE_FIELD(src.port, NOMERGE), PF_RULE_FIELD(dst.port, NOMERGE), PF_RULE_FIELD(src.port_op, NOMERGE), PF_RULE_FIELD(dst.port_op, NOMERGE), PF_RULE_FIELD(src.neg, NOMERGE), PF_RULE_FIELD(dst.neg, NOMERGE), /* These fields can be merged */ PF_RULE_FIELD(src.addr, COMBINED), PF_RULE_FIELD(dst.addr, COMBINED), /* We just don't care about these fields. They're set by the kernel */ PF_RULE_FIELD(skip, DC), PF_RULE_FIELD(evaluations, DC), PF_RULE_FIELD(packets, DC), PF_RULE_FIELD(bytes, DC), PF_RULE_FIELD(kif, DC), PF_RULE_FIELD(states_cur, DC), PF_RULE_FIELD(states_tot, DC), PF_RULE_FIELD(src_nodes, DC), PF_RULE_FIELD(nr, DC), PF_RULE_FIELD(entries, DC), PF_RULE_FIELD(qid, DC), PF_RULE_FIELD(pqid, DC), PF_RULE_FIELD(anchor_relative, DC), PF_RULE_FIELD(anchor_wildcard, DC), PF_RULE_FIELD(tag, DC), PF_RULE_FIELD(match_tag, DC), PF_RULE_FIELD(overload_tbl, DC), /* These fields should never be set in a PASS/BLOCK rule */ PF_RULE_FIELD(natpass, NEVER), PF_RULE_FIELD(max_mss, NEVER), PF_RULE_FIELD(min_ttl, NEVER), PF_RULE_FIELD(set_tos, NEVER), }; int add_opt_table(struct pfctl *, struct pf_opt_tbl **, sa_family_t, struct pf_rule_addr *); int addrs_combineable(struct pf_rule_addr *, struct pf_rule_addr *); int addrs_equal(struct pf_rule_addr *, struct pf_rule_addr *); int block_feedback(struct pfctl *, struct superblock *); int combine_rules(struct pfctl *, struct superblock *); -void comparable_rule(struct pf_rule *, const struct pf_rule *, int); +void comparable_rule(struct pfctl_rule *, const struct pfctl_rule *, int); int construct_superblocks(struct pfctl *, struct pf_opt_queue *, struct superblocks *); -void exclude_supersets(struct pf_rule *, struct pf_rule *); +void exclude_supersets(struct pfctl_rule *, struct pfctl_rule *); int interface_group(const char *); int load_feedback_profile(struct pfctl *, struct superblocks *); int optimize_superblock(struct pfctl *, struct superblock *); int pf_opt_create_table(struct pfctl *, struct pf_opt_tbl *); void remove_from_skipsteps(struct skiplist *, struct superblock *, struct pf_opt_rule *, struct pf_skip_step *); int remove_identical_rules(struct pfctl *, struct superblock *); int reorder_rules(struct pfctl *, struct superblock *, int); -int rules_combineable(struct pf_rule *, struct pf_rule *); +int rules_combineable(struct pfctl_rule *, struct pfctl_rule *); void skip_append(struct superblock *, int, struct pf_skip_step *, struct pf_opt_rule *); int skip_compare(int, struct pf_skip_step *, struct pf_opt_rule *); void skip_init(void); -int skip_cmp_af(struct pf_rule *, struct pf_rule *); -int skip_cmp_dir(struct pf_rule *, struct pf_rule *); -int skip_cmp_dst_addr(struct pf_rule *, struct pf_rule *); -int skip_cmp_dst_port(struct pf_rule *, struct pf_rule *); -int skip_cmp_ifp(struct pf_rule *, struct pf_rule *); -int skip_cmp_proto(struct pf_rule *, struct pf_rule *); -int skip_cmp_src_addr(struct pf_rule *, struct pf_rule *); -int skip_cmp_src_port(struct pf_rule *, struct pf_rule *); +int skip_cmp_af(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_dir(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_dst_addr(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_dst_port(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_ifp(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_proto(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_src_addr(struct pfctl_rule *, struct pfctl_rule *); +int skip_cmp_src_port(struct pfctl_rule *, struct pfctl_rule *); int superblock_inclusive(struct superblock *, struct pf_opt_rule *); void superblock_free(struct pfctl *, struct superblock *); -static int (*skip_comparitors[PF_SKIP_COUNT])(struct pf_rule *, - struct pf_rule *); +static int (*skip_comparitors[PF_SKIP_COUNT])(struct pfctl_rule *, + struct pfctl_rule *); static const char *skip_comparitors_names[PF_SKIP_COUNT]; #define PF_SKIP_COMPARITORS { \ { "ifp", PF_SKIP_IFP, skip_cmp_ifp }, \ { "dir", PF_SKIP_DIR, skip_cmp_dir }, \ { "af", PF_SKIP_AF, skip_cmp_af }, \ { "proto", PF_SKIP_PROTO, skip_cmp_proto }, \ { "saddr", PF_SKIP_SRC_ADDR, skip_cmp_src_addr }, \ { "sport", PF_SKIP_SRC_PORT, skip_cmp_src_port }, \ { "daddr", PF_SKIP_DST_ADDR, skip_cmp_dst_addr }, \ { "dport", PF_SKIP_DST_PORT, skip_cmp_dst_port } \ } static struct pfr_buffer table_buffer; static int table_identifier; int -pfctl_optimize_ruleset(struct pfctl *pf, struct pf_ruleset *rs) +pfctl_optimize_ruleset(struct pfctl *pf, struct pfctl_ruleset *rs) { struct superblocks superblocks; struct pf_opt_queue opt_queue; struct superblock *block; struct pf_opt_rule *por; - struct pf_rule *r; - struct pf_rulequeue *old_rules; + struct pfctl_rule *r; + struct pfctl_rulequeue *old_rules; DEBUG("optimizing ruleset"); memset(&table_buffer, 0, sizeof(table_buffer)); skip_init(); TAILQ_INIT(&opt_queue); old_rules = rs->rules[PF_RULESET_FILTER].active.ptr; rs->rules[PF_RULESET_FILTER].active.ptr = rs->rules[PF_RULESET_FILTER].inactive.ptr; rs->rules[PF_RULESET_FILTER].inactive.ptr = old_rules; /* * XXX expanding the pf_opt_rule format throughout pfctl might allow * us to avoid all this copying. */ while ((r = TAILQ_FIRST(rs->rules[PF_RULESET_FILTER].inactive.ptr)) != NULL) { TAILQ_REMOVE(rs->rules[PF_RULESET_FILTER].inactive.ptr, r, entries); if ((por = calloc(1, sizeof(*por))) == NULL) err(1, "calloc"); memcpy(&por->por_rule, r, sizeof(*r)); if (TAILQ_FIRST(&r->rpool.list) != NULL) { TAILQ_INIT(&por->por_rule.rpool.list); pfctl_move_pool(&r->rpool, &por->por_rule.rpool); } else bzero(&por->por_rule.rpool, sizeof(por->por_rule.rpool)); TAILQ_INSERT_TAIL(&opt_queue, por, por_entry); } TAILQ_INIT(&superblocks); if (construct_superblocks(pf, &opt_queue, &superblocks)) goto error; if (pf->optimize & PF_OPTIMIZE_PROFILE) { if (load_feedback_profile(pf, &superblocks)) goto error; } TAILQ_FOREACH(block, &superblocks, sb_entry) { if (optimize_superblock(pf, block)) goto error; } rs->anchor->refcnt = 0; while ((block = TAILQ_FIRST(&superblocks))) { TAILQ_REMOVE(&superblocks, block, sb_entry); while ((por = TAILQ_FIRST(&block->sb_rules))) { TAILQ_REMOVE(&block->sb_rules, por, por_entry); por->por_rule.nr = rs->anchor->refcnt++; if ((r = calloc(1, sizeof(*r))) == NULL) err(1, "calloc"); memcpy(r, &por->por_rule, sizeof(*r)); TAILQ_INIT(&r->rpool.list); pfctl_move_pool(&por->por_rule.rpool, &r->rpool); TAILQ_INSERT_TAIL( rs->rules[PF_RULESET_FILTER].active.ptr, r, entries); free(por); } free(block); } return (0); error: while ((por = TAILQ_FIRST(&opt_queue))) { TAILQ_REMOVE(&opt_queue, por, por_entry); if (por->por_src_tbl) { pfr_buf_clear(por->por_src_tbl->pt_buf); free(por->por_src_tbl->pt_buf); free(por->por_src_tbl); } if (por->por_dst_tbl) { pfr_buf_clear(por->por_dst_tbl->pt_buf); free(por->por_dst_tbl->pt_buf); free(por->por_dst_tbl); } free(por); } while ((block = TAILQ_FIRST(&superblocks))) { TAILQ_REMOVE(&superblocks, block, sb_entry); superblock_free(pf, block); } return (1); } /* * Go ahead and optimize a superblock */ int optimize_superblock(struct pfctl *pf, struct superblock *block) { #ifdef OPT_DEBUG struct pf_opt_rule *por; #endif /* OPT_DEBUG */ /* We have a few optimization passes: * 1) remove duplicate rules or rules that are a subset of other * rules * 2) combine otherwise identical rules with different IP addresses * into a single rule and put the addresses in a table. * 3) re-order the rules to improve kernel skip steps * 4) re-order the 'quick' rules based on feedback from the * active ruleset statistics * * XXX combine_rules() doesn't combine v4 and v6 rules. would just * have to keep af in the table container, make af 'COMBINE' and * twiddle the af on the merged rule * XXX maybe add a weighting to the metric on skipsteps when doing * reordering. sometimes two sequential tables will be better * that four consecutive interfaces. * XXX need to adjust the skipstep count of everything after PROTO, * since they aren't actually checked on a proto mismatch in * pf_test_{tcp, udp, icmp}() * XXX should i treat proto=0, af=0 or dir=0 special in skepstep * calculation since they are a DC? * XXX keep last skiplist of last superblock to influence this * superblock. '5 inet6 log' should make '3 inet6' come before '4 * inet' in the next superblock. * XXX would be useful to add tables for ports * XXX we can also re-order some mutually exclusive superblocks to * try merging superblocks before any of these optimization passes. * for instance a single 'log in' rule in the middle of non-logging * out rules. */ /* shortcut. there will be a lot of 1-rule superblocks */ if (!TAILQ_NEXT(TAILQ_FIRST(&block->sb_rules), por_entry)) return (0); #ifdef OPT_DEBUG printf("--- Superblock ---\n"); TAILQ_FOREACH(por, &block->sb_rules, por_entry) { printf(" "); print_rule(&por->por_rule, por->por_rule.anchor ? por->por_rule.anchor->name : "", 1, 0); } #endif /* OPT_DEBUG */ if (remove_identical_rules(pf, block)) return (1); if (combine_rules(pf, block)) return (1); if ((pf->optimize & PF_OPTIMIZE_PROFILE) && TAILQ_FIRST(&block->sb_rules)->por_rule.quick && block->sb_profiled_block) { if (block_feedback(pf, block)) return (1); } else if (reorder_rules(pf, block, 0)) { return (1); } /* * Don't add any optimization passes below reorder_rules(). It will * have divided superblocks into smaller blocks for further refinement * and doesn't put them back together again. What once was a true * superblock might have been split into multiple superblocks. */ #ifdef OPT_DEBUG printf("--- END Superblock ---\n"); #endif /* OPT_DEBUG */ return (0); } /* * Optimization pass #1: remove identical rules */ int remove_identical_rules(struct pfctl *pf, struct superblock *block) { struct pf_opt_rule *por1, *por2, *por_next, *por2_next; - struct pf_rule a, a2, b, b2; + struct pfctl_rule a, a2, b, b2; for (por1 = TAILQ_FIRST(&block->sb_rules); por1; por1 = por_next) { por_next = TAILQ_NEXT(por1, por_entry); for (por2 = por_next; por2; por2 = por2_next) { por2_next = TAILQ_NEXT(por2, por_entry); comparable_rule(&a, &por1->por_rule, DC); comparable_rule(&b, &por2->por_rule, DC); memcpy(&a2, &a, sizeof(a2)); memcpy(&b2, &b, sizeof(b2)); exclude_supersets(&a, &b); exclude_supersets(&b2, &a2); if (memcmp(&a, &b, sizeof(a)) == 0) { DEBUG("removing identical rule nr%d = *nr%d*", por1->por_rule.nr, por2->por_rule.nr); TAILQ_REMOVE(&block->sb_rules, por2, por_entry); if (por_next == por2) por_next = TAILQ_NEXT(por1, por_entry); free(por2); } else if (memcmp(&a2, &b2, sizeof(a2)) == 0) { DEBUG("removing identical rule *nr%d* = nr%d", por1->por_rule.nr, por2->por_rule.nr); TAILQ_REMOVE(&block->sb_rules, por1, por_entry); free(por1); break; } } } return (0); } /* * Optimization pass #2: combine similar rules with different addresses * into a single rule and a table */ int combine_rules(struct pfctl *pf, struct superblock *block) { struct pf_opt_rule *p1, *p2, *por_next; int src_eq, dst_eq; if ((pf->loadopt & PFCTL_FLAG_TABLE) == 0) { warnx("Must enable table loading for optimizations"); return (1); } /* First we make a pass to combine the rules. O(n log n) */ TAILQ_FOREACH(p1, &block->sb_rules, por_entry) { for (p2 = TAILQ_NEXT(p1, por_entry); p2; p2 = por_next) { por_next = TAILQ_NEXT(p2, por_entry); src_eq = addrs_equal(&p1->por_rule.src, &p2->por_rule.src); dst_eq = addrs_equal(&p1->por_rule.dst, &p2->por_rule.dst); if (src_eq && !dst_eq && p1->por_src_tbl == NULL && p2->por_dst_tbl == NULL && p2->por_src_tbl == NULL && rules_combineable(&p1->por_rule, &p2->por_rule) && addrs_combineable(&p1->por_rule.dst, &p2->por_rule.dst)) { DEBUG("can combine rules nr%d = nr%d", p1->por_rule.nr, p2->por_rule.nr); if (p1->por_dst_tbl == NULL && add_opt_table(pf, &p1->por_dst_tbl, p1->por_rule.af, &p1->por_rule.dst)) return (1); if (add_opt_table(pf, &p1->por_dst_tbl, p1->por_rule.af, &p2->por_rule.dst)) return (1); p2->por_dst_tbl = p1->por_dst_tbl; if (p1->por_dst_tbl->pt_rulecount >= TABLE_THRESHOLD) { TAILQ_REMOVE(&block->sb_rules, p2, por_entry); free(p2); } } else if (!src_eq && dst_eq && p1->por_dst_tbl == NULL && p2->por_src_tbl == NULL && p2->por_dst_tbl == NULL && rules_combineable(&p1->por_rule, &p2->por_rule) && addrs_combineable(&p1->por_rule.src, &p2->por_rule.src)) { DEBUG("can combine rules nr%d = nr%d", p1->por_rule.nr, p2->por_rule.nr); if (p1->por_src_tbl == NULL && add_opt_table(pf, &p1->por_src_tbl, p1->por_rule.af, &p1->por_rule.src)) return (1); if (add_opt_table(pf, &p1->por_src_tbl, p1->por_rule.af, &p2->por_rule.src)) return (1); p2->por_src_tbl = p1->por_src_tbl; if (p1->por_src_tbl->pt_rulecount >= TABLE_THRESHOLD) { TAILQ_REMOVE(&block->sb_rules, p2, por_entry); free(p2); } } } } /* * Then we make a final pass to create a valid table name and * insert the name into the rules. */ for (p1 = TAILQ_FIRST(&block->sb_rules); p1; p1 = por_next) { por_next = TAILQ_NEXT(p1, por_entry); assert(p1->por_src_tbl == NULL || p1->por_dst_tbl == NULL); if (p1->por_src_tbl && p1->por_src_tbl->pt_rulecount >= TABLE_THRESHOLD) { if (p1->por_src_tbl->pt_generated) { /* This rule is included in a table */ TAILQ_REMOVE(&block->sb_rules, p1, por_entry); free(p1); continue; } p1->por_src_tbl->pt_generated = 1; if ((pf->opts & PF_OPT_NOACTION) == 0 && pf_opt_create_table(pf, p1->por_src_tbl)) return (1); pf->tdirty = 1; if (pf->opts & PF_OPT_VERBOSE) print_tabledef(p1->por_src_tbl->pt_name, PFR_TFLAG_CONST, 1, &p1->por_src_tbl->pt_nodes); memset(&p1->por_rule.src.addr, 0, sizeof(p1->por_rule.src.addr)); p1->por_rule.src.addr.type = PF_ADDR_TABLE; strlcpy(p1->por_rule.src.addr.v.tblname, p1->por_src_tbl->pt_name, sizeof(p1->por_rule.src.addr.v.tblname)); pfr_buf_clear(p1->por_src_tbl->pt_buf); free(p1->por_src_tbl->pt_buf); p1->por_src_tbl->pt_buf = NULL; } if (p1->por_dst_tbl && p1->por_dst_tbl->pt_rulecount >= TABLE_THRESHOLD) { if (p1->por_dst_tbl->pt_generated) { /* This rule is included in a table */ TAILQ_REMOVE(&block->sb_rules, p1, por_entry); free(p1); continue; } p1->por_dst_tbl->pt_generated = 1; if ((pf->opts & PF_OPT_NOACTION) == 0 && pf_opt_create_table(pf, p1->por_dst_tbl)) return (1); pf->tdirty = 1; if (pf->opts & PF_OPT_VERBOSE) print_tabledef(p1->por_dst_tbl->pt_name, PFR_TFLAG_CONST, 1, &p1->por_dst_tbl->pt_nodes); memset(&p1->por_rule.dst.addr, 0, sizeof(p1->por_rule.dst.addr)); p1->por_rule.dst.addr.type = PF_ADDR_TABLE; strlcpy(p1->por_rule.dst.addr.v.tblname, p1->por_dst_tbl->pt_name, sizeof(p1->por_rule.dst.addr.v.tblname)); pfr_buf_clear(p1->por_dst_tbl->pt_buf); free(p1->por_dst_tbl->pt_buf); p1->por_dst_tbl->pt_buf = NULL; } } return (0); } /* * Optimization pass #3: re-order rules to improve skip steps */ int reorder_rules(struct pfctl *pf, struct superblock *block, int depth) { struct superblock *newblock; struct pf_skip_step *skiplist; struct pf_opt_rule *por; int i, largest, largest_list, rule_count = 0; TAILQ_HEAD( , pf_opt_rule) head; /* * Calculate the best-case skip steps. We put each rule in a list * of other rules with common fields */ for (i = 0; i < PF_SKIP_COUNT; i++) { TAILQ_FOREACH(por, &block->sb_rules, por_entry) { TAILQ_FOREACH(skiplist, &block->sb_skipsteps[i], ps_entry) { if (skip_compare(i, skiplist, por) == 0) break; } if (skiplist == NULL) { if ((skiplist = calloc(1, sizeof(*skiplist))) == NULL) err(1, "calloc"); TAILQ_INIT(&skiplist->ps_rules); TAILQ_INSERT_TAIL(&block->sb_skipsteps[i], skiplist, ps_entry); } skip_append(block, i, skiplist, por); } } TAILQ_FOREACH(por, &block->sb_rules, por_entry) rule_count++; /* * Now we're going to ignore any fields that are identical between * all of the rules in the superblock and those fields which differ * between every rule in the superblock. */ largest = 0; for (i = 0; i < PF_SKIP_COUNT; i++) { skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]); if (skiplist->ps_count == rule_count) { DEBUG("(%d) original skipstep '%s' is all rules", depth, skip_comparitors_names[i]); skiplist->ps_count = 0; } else if (skiplist->ps_count == 1) { skiplist->ps_count = 0; } else { DEBUG("(%d) original skipstep '%s' largest jump is %d", depth, skip_comparitors_names[i], skiplist->ps_count); if (skiplist->ps_count > largest) largest = skiplist->ps_count; } } if (largest == 0) { /* Ugh. There is NO commonality in the superblock on which * optimize the skipsteps optimization. */ goto done; } /* * Now we're going to empty the superblock rule list and re-create * it based on a more optimal skipstep order. */ TAILQ_INIT(&head); while ((por = TAILQ_FIRST(&block->sb_rules))) { TAILQ_REMOVE(&block->sb_rules, por, por_entry); TAILQ_INSERT_TAIL(&head, por, por_entry); } while (!TAILQ_EMPTY(&head)) { largest = 1; /* * Find the most useful skip steps remaining */ for (i = 0; i < PF_SKIP_COUNT; i++) { skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]); if (skiplist->ps_count > largest) { largest = skiplist->ps_count; largest_list = i; } } if (largest <= 1) { /* * Nothing useful left. Leave remaining rules in order. */ DEBUG("(%d) no more commonality for skip steps", depth); while ((por = TAILQ_FIRST(&head))) { TAILQ_REMOVE(&head, por, por_entry); TAILQ_INSERT_TAIL(&block->sb_rules, por, por_entry); } } else { /* * There is commonality. Extract those common rules * and place them in the ruleset adjacent to each * other. */ skiplist = TAILQ_FIRST(&block->sb_skipsteps[ largest_list]); DEBUG("(%d) skipstep '%s' largest jump is %d @ #%d", depth, skip_comparitors_names[largest_list], largest, TAILQ_FIRST(&TAILQ_FIRST(&block-> sb_skipsteps [largest_list])->ps_rules)-> por_rule.nr); TAILQ_REMOVE(&block->sb_skipsteps[largest_list], skiplist, ps_entry); /* * There may be further commonality inside these * rules. So we'll split them off into they're own * superblock and pass it back into the optimizer. */ if (skiplist->ps_count > 2) { if ((newblock = calloc(1, sizeof(*newblock))) == NULL) { warn("calloc"); return (1); } TAILQ_INIT(&newblock->sb_rules); for (i = 0; i < PF_SKIP_COUNT; i++) TAILQ_INIT(&newblock->sb_skipsteps[i]); TAILQ_INSERT_BEFORE(block, newblock, sb_entry); DEBUG("(%d) splitting off %d rules from superblock @ #%d", depth, skiplist->ps_count, TAILQ_FIRST(&skiplist->ps_rules)-> por_rule.nr); } else { newblock = block; } while ((por = TAILQ_FIRST(&skiplist->ps_rules))) { TAILQ_REMOVE(&head, por, por_entry); TAILQ_REMOVE(&skiplist->ps_rules, por, por_skip_entry[largest_list]); TAILQ_INSERT_TAIL(&newblock->sb_rules, por, por_entry); /* Remove this rule from all other skiplists */ remove_from_skipsteps(&block->sb_skipsteps[ largest_list], block, por, skiplist); } free(skiplist); if (newblock != block) if (reorder_rules(pf, newblock, depth + 1)) return (1); } } done: for (i = 0; i < PF_SKIP_COUNT; i++) { while ((skiplist = TAILQ_FIRST(&block->sb_skipsteps[i]))) { TAILQ_REMOVE(&block->sb_skipsteps[i], skiplist, ps_entry); free(skiplist); } } return (0); } /* * Optimization pass #4: re-order 'quick' rules based on feedback from the * currently running ruleset */ int block_feedback(struct pfctl *pf, struct superblock *block) { TAILQ_HEAD( , pf_opt_rule) queue; struct pf_opt_rule *por1, *por2; u_int64_t total_count = 0; - struct pf_rule a, b; + struct pfctl_rule a, b; /* * Walk through all of the profiled superblock's rules and copy * the counters onto our rules. */ TAILQ_FOREACH(por1, &block->sb_profiled_block->sb_rules, por_entry) { comparable_rule(&a, &por1->por_rule, DC); total_count += por1->por_rule.packets[0] + por1->por_rule.packets[1]; TAILQ_FOREACH(por2, &block->sb_rules, por_entry) { if (por2->por_profile_count) continue; comparable_rule(&b, &por2->por_rule, DC); if (memcmp(&a, &b, sizeof(a)) == 0) { por2->por_profile_count = por1->por_rule.packets[0] + por1->por_rule.packets[1]; break; } } } superblock_free(pf, block->sb_profiled_block); block->sb_profiled_block = NULL; /* * Now we pull all of the rules off the superblock and re-insert them * in sorted order. */ TAILQ_INIT(&queue); while ((por1 = TAILQ_FIRST(&block->sb_rules)) != NULL) { TAILQ_REMOVE(&block->sb_rules, por1, por_entry); TAILQ_INSERT_TAIL(&queue, por1, por_entry); } while ((por1 = TAILQ_FIRST(&queue)) != NULL) { TAILQ_REMOVE(&queue, por1, por_entry); /* XXX I should sort all of the unused rules based on skip steps */ TAILQ_FOREACH(por2, &block->sb_rules, por_entry) { if (por1->por_profile_count > por2->por_profile_count) { TAILQ_INSERT_BEFORE(por2, por1, por_entry); break; } } #ifdef __FreeBSD__ if (por2 == NULL) #else if (por2 == TAILQ_END(&block->sb_rules)) #endif TAILQ_INSERT_TAIL(&block->sb_rules, por1, por_entry); } return (0); } /* * Load the current ruleset from the kernel and try to associate them with * the ruleset we're optimizing. */ int load_feedback_profile(struct pfctl *pf, struct superblocks *superblocks) { struct superblock *block, *blockcur; struct superblocks prof_superblocks; struct pf_opt_rule *por; struct pf_opt_queue queue; struct pfioc_rule pr; - struct pf_rule a, b; + struct pfctl_rule a, b, rule; int nr, mnr; TAILQ_INIT(&queue); TAILQ_INIT(&prof_superblocks); memset(&pr, 0, sizeof(pr)); pr.rule.action = PF_PASS; if (ioctl(pf->dev, DIOCGETRULES, &pr)) { warn("DIOCGETRULES"); return (1); } mnr = pr.nr; DEBUG("Loading %d active rules for a feedback profile", mnr); for (nr = 0; nr < mnr; ++nr) { - struct pf_ruleset *rs; + struct pfctl_ruleset *rs; if ((por = calloc(1, sizeof(*por))) == NULL) { warn("calloc"); return (1); } pr.nr = nr; if (pfctl_get_rule(pf->dev, nr, pr.ticket, "", PF_PASS, - &pr.rule, pr.anchor_call)) { + &rule, pr.anchor_call)) { warn("DIOCGETRULENV"); return (1); } - memcpy(&por->por_rule, &pr.rule, sizeof(por->por_rule)); + memcpy(&por->por_rule, &rule, sizeof(por->por_rule)); rs = pf_find_or_create_ruleset(pr.anchor_call); por->por_rule.anchor = rs->anchor; if (TAILQ_EMPTY(&por->por_rule.rpool.list)) memset(&por->por_rule.rpool, 0, sizeof(por->por_rule.rpool)); TAILQ_INSERT_TAIL(&queue, por, por_entry); - /* XXX pfctl_get_pool(pf->dev, &pr.rule.rpool, nr, pr.ticket, + /* XXX pfctl_get_pool(pf->dev, &rule.rpool, nr, pr.ticket, * PF_PASS, pf->anchor) ??? - * ... pfctl_clear_pool(&pr.rule.rpool) + * ... pfctl_clear_pool(&rule.rpool) */ } if (construct_superblocks(pf, &queue, &prof_superblocks)) return (1); /* * Now we try to associate the active ruleset's superblocks with * the superblocks we're compiling. */ block = TAILQ_FIRST(superblocks); blockcur = TAILQ_FIRST(&prof_superblocks); while (block && blockcur) { comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule, BREAK); comparable_rule(&b, &TAILQ_FIRST(&blockcur->sb_rules)->por_rule, BREAK); if (memcmp(&a, &b, sizeof(a)) == 0) { /* The two superblocks lined up */ block->sb_profiled_block = blockcur; } else { DEBUG("superblocks don't line up between #%d and #%d", TAILQ_FIRST(&block->sb_rules)->por_rule.nr, TAILQ_FIRST(&blockcur->sb_rules)->por_rule.nr); break; } block = TAILQ_NEXT(block, sb_entry); blockcur = TAILQ_NEXT(blockcur, sb_entry); } /* Free any superblocks we couldn't link */ while (blockcur) { block = TAILQ_NEXT(blockcur, sb_entry); superblock_free(pf, blockcur); blockcur = block; } return (0); } /* * Compare a rule to a skiplist to see if the rule is a member */ int skip_compare(int skipnum, struct pf_skip_step *skiplist, struct pf_opt_rule *por) { - struct pf_rule *a, *b; + struct pfctl_rule *a, *b; if (skipnum >= PF_SKIP_COUNT || skipnum < 0) errx(1, "skip_compare() out of bounds"); a = &por->por_rule; b = &TAILQ_FIRST(&skiplist->ps_rules)->por_rule; return ((skip_comparitors[skipnum])(a, b)); } /* * Add a rule to a skiplist */ void skip_append(struct superblock *superblock, int skipnum, struct pf_skip_step *skiplist, struct pf_opt_rule *por) { struct pf_skip_step *prev; skiplist->ps_count++; TAILQ_INSERT_TAIL(&skiplist->ps_rules, por, por_skip_entry[skipnum]); /* Keep the list of skiplists sorted by whichever is larger */ while ((prev = TAILQ_PREV(skiplist, skiplist, ps_entry)) && prev->ps_count < skiplist->ps_count) { TAILQ_REMOVE(&superblock->sb_skipsteps[skipnum], skiplist, ps_entry); TAILQ_INSERT_BEFORE(prev, skiplist, ps_entry); } } /* * Remove a rule from the other skiplist calculations. */ void remove_from_skipsteps(struct skiplist *head, struct superblock *block, struct pf_opt_rule *por, struct pf_skip_step *active_list) { struct pf_skip_step *sk, *next; struct pf_opt_rule *p2; int i, found; for (i = 0; i < PF_SKIP_COUNT; i++) { sk = TAILQ_FIRST(&block->sb_skipsteps[i]); if (sk == NULL || sk == active_list || sk->ps_count <= 1) continue; found = 0; do { TAILQ_FOREACH(p2, &sk->ps_rules, por_skip_entry[i]) if (p2 == por) { TAILQ_REMOVE(&sk->ps_rules, p2, por_skip_entry[i]); found = 1; sk->ps_count--; break; } } while (!found && (sk = TAILQ_NEXT(sk, ps_entry))); if (found && sk) { /* Does this change the sorting order? */ while ((next = TAILQ_NEXT(sk, ps_entry)) && next->ps_count > sk->ps_count) { TAILQ_REMOVE(head, sk, ps_entry); TAILQ_INSERT_AFTER(head, next, sk, ps_entry); } #ifdef OPT_DEBUG next = TAILQ_NEXT(sk, ps_entry); assert(next == NULL || next->ps_count <= sk->ps_count); #endif /* OPT_DEBUG */ } } } /* Compare two rules AF field for skiplist construction */ int -skip_cmp_af(struct pf_rule *a, struct pf_rule *b) +skip_cmp_af(struct pfctl_rule *a, struct pfctl_rule *b) { if (a->af != b->af || a->af == 0) return (1); return (0); } /* Compare two rules DIRECTION field for skiplist construction */ int -skip_cmp_dir(struct pf_rule *a, struct pf_rule *b) +skip_cmp_dir(struct pfctl_rule *a, struct pfctl_rule *b) { if (a->direction == 0 || a->direction != b->direction) return (1); return (0); } /* Compare two rules DST Address field for skiplist construction */ int -skip_cmp_dst_addr(struct pf_rule *a, struct pf_rule *b) +skip_cmp_dst_addr(struct pfctl_rule *a, struct pfctl_rule *b) { if (a->dst.neg != b->dst.neg || a->dst.addr.type != b->dst.addr.type) return (1); /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || * a->proto == IPPROTO_ICMP * return (1); */ switch (a->dst.addr.type) { case PF_ADDR_ADDRMASK: if (memcmp(&a->dst.addr.v.a.addr, &b->dst.addr.v.a.addr, sizeof(a->dst.addr.v.a.addr)) || memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask, sizeof(a->dst.addr.v.a.mask)) || (a->dst.addr.v.a.addr.addr32[0] == 0 && a->dst.addr.v.a.addr.addr32[1] == 0 && a->dst.addr.v.a.addr.addr32[2] == 0 && a->dst.addr.v.a.addr.addr32[3] == 0)) return (1); return (0); case PF_ADDR_DYNIFTL: if (strcmp(a->dst.addr.v.ifname, b->dst.addr.v.ifname) != 0 || a->dst.addr.iflags != b->dst.addr.iflags || memcmp(&a->dst.addr.v.a.mask, &b->dst.addr.v.a.mask, sizeof(a->dst.addr.v.a.mask))) return (1); return (0); case PF_ADDR_NOROUTE: case PF_ADDR_URPFFAILED: return (0); case PF_ADDR_TABLE: return (strcmp(a->dst.addr.v.tblname, b->dst.addr.v.tblname)); } return (1); } /* Compare two rules DST port field for skiplist construction */ int -skip_cmp_dst_port(struct pf_rule *a, struct pf_rule *b) +skip_cmp_dst_port(struct pfctl_rule *a, struct pfctl_rule *b) { /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || * a->proto == IPPROTO_ICMP * return (1); */ if (a->dst.port_op == PF_OP_NONE || a->dst.port_op != b->dst.port_op || a->dst.port[0] != b->dst.port[0] || a->dst.port[1] != b->dst.port[1]) return (1); return (0); } /* Compare two rules IFP field for skiplist construction */ int -skip_cmp_ifp(struct pf_rule *a, struct pf_rule *b) +skip_cmp_ifp(struct pfctl_rule *a, struct pfctl_rule *b) { if (strcmp(a->ifname, b->ifname) || a->ifname[0] == '\0') return (1); return (a->ifnot != b->ifnot); } /* Compare two rules PROTO field for skiplist construction */ int -skip_cmp_proto(struct pf_rule *a, struct pf_rule *b) +skip_cmp_proto(struct pfctl_rule *a, struct pfctl_rule *b) { return (a->proto != b->proto || a->proto == 0); } /* Compare two rules SRC addr field for skiplist construction */ int -skip_cmp_src_addr(struct pf_rule *a, struct pf_rule *b) +skip_cmp_src_addr(struct pfctl_rule *a, struct pfctl_rule *b) { if (a->src.neg != b->src.neg || a->src.addr.type != b->src.addr.type) return (1); /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || * a->proto == IPPROTO_ICMP * return (1); */ switch (a->src.addr.type) { case PF_ADDR_ADDRMASK: if (memcmp(&a->src.addr.v.a.addr, &b->src.addr.v.a.addr, sizeof(a->src.addr.v.a.addr)) || memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask, sizeof(a->src.addr.v.a.mask)) || (a->src.addr.v.a.addr.addr32[0] == 0 && a->src.addr.v.a.addr.addr32[1] == 0 && a->src.addr.v.a.addr.addr32[2] == 0 && a->src.addr.v.a.addr.addr32[3] == 0)) return (1); return (0); case PF_ADDR_DYNIFTL: if (strcmp(a->src.addr.v.ifname, b->src.addr.v.ifname) != 0 || a->src.addr.iflags != b->src.addr.iflags || memcmp(&a->src.addr.v.a.mask, &b->src.addr.v.a.mask, sizeof(a->src.addr.v.a.mask))) return (1); return (0); case PF_ADDR_NOROUTE: case PF_ADDR_URPFFAILED: return (0); case PF_ADDR_TABLE: return (strcmp(a->src.addr.v.tblname, b->src.addr.v.tblname)); } return (1); } /* Compare two rules SRC port field for skiplist construction */ int -skip_cmp_src_port(struct pf_rule *a, struct pf_rule *b) +skip_cmp_src_port(struct pfctl_rule *a, struct pfctl_rule *b) { if (a->src.port_op == PF_OP_NONE || a->src.port_op != b->src.port_op || a->src.port[0] != b->src.port[0] || a->src.port[1] != b->src.port[1]) return (1); /* XXX if (a->proto != b->proto && a->proto != 0 && b->proto != 0 * && (a->proto == IPPROTO_TCP || a->proto == IPPROTO_UDP || * a->proto == IPPROTO_ICMP * return (1); */ return (0); } void skip_init(void) { struct { char *name; int skipnum; - int (*func)(struct pf_rule *, struct pf_rule *); + int (*func)(struct pfctl_rule *, struct pfctl_rule *); } comps[] = PF_SKIP_COMPARITORS; int skipnum, i; for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++) { for (i = 0; i < sizeof(comps)/sizeof(*comps); i++) if (comps[i].skipnum == skipnum) { skip_comparitors[skipnum] = comps[i].func; skip_comparitors_names[skipnum] = comps[i].name; } } for (skipnum = 0; skipnum < PF_SKIP_COUNT; skipnum++) if (skip_comparitors[skipnum] == NULL) errx(1, "Need to add skip step comparitor to pfctl?!"); } /* * Add a host/netmask to a table */ int add_opt_table(struct pfctl *pf, struct pf_opt_tbl **tbl, sa_family_t af, struct pf_rule_addr *addr) { #ifdef OPT_DEBUG char buf[128]; #endif /* OPT_DEBUG */ static int tablenum = 0; struct node_host node_host; if (*tbl == NULL) { if ((*tbl = calloc(1, sizeof(**tbl))) == NULL || ((*tbl)->pt_buf = calloc(1, sizeof(*(*tbl)->pt_buf))) == NULL) err(1, "calloc"); (*tbl)->pt_buf->pfrb_type = PFRB_ADDRS; SIMPLEQ_INIT(&(*tbl)->pt_nodes); /* This is just a temporary table name */ snprintf((*tbl)->pt_name, sizeof((*tbl)->pt_name), "%s%d", PF_OPT_TABLE_PREFIX, tablenum++); DEBUG("creating table <%s>", (*tbl)->pt_name); } memset(&node_host, 0, sizeof(node_host)); node_host.af = af; node_host.addr = addr->addr; #ifdef OPT_DEBUG DEBUG("<%s> adding %s/%d", (*tbl)->pt_name, inet_ntop(af, &node_host.addr.v.a.addr, buf, sizeof(buf)), unmask(&node_host.addr.v.a.mask, af)); #endif /* OPT_DEBUG */ if (append_addr_host((*tbl)->pt_buf, &node_host, 0, 0)) { warn("failed to add host"); return (1); } if (pf->opts & PF_OPT_VERBOSE) { struct node_tinit *ti; if ((ti = calloc(1, sizeof(*ti))) == NULL) err(1, "malloc"); if ((ti->host = malloc(sizeof(*ti->host))) == NULL) err(1, "malloc"); memcpy(ti->host, &node_host, sizeof(*ti->host)); SIMPLEQ_INSERT_TAIL(&(*tbl)->pt_nodes, ti, entries); } (*tbl)->pt_rulecount++; if ((*tbl)->pt_rulecount == TABLE_THRESHOLD) DEBUG("table <%s> now faster than skip steps", (*tbl)->pt_name); return (0); } /* * Do the dirty work of choosing an unused table name and creating it. * (be careful with the table name, it might already be used in another anchor) */ int pf_opt_create_table(struct pfctl *pf, struct pf_opt_tbl *tbl) { static int tablenum; struct pfr_table *t; if (table_buffer.pfrb_type == 0) { /* Initialize the list of tables */ table_buffer.pfrb_type = PFRB_TABLES; for (;;) { pfr_buf_grow(&table_buffer, table_buffer.pfrb_size); table_buffer.pfrb_size = table_buffer.pfrb_msize; if (pfr_get_tables(NULL, table_buffer.pfrb_caddr, &table_buffer.pfrb_size, PFR_FLAG_ALLRSETS)) err(1, "pfr_get_tables"); if (table_buffer.pfrb_size <= table_buffer.pfrb_msize) break; } table_identifier = arc4random(); } /* XXX would be *really* nice to avoid duplicating identical tables */ /* Now we have to pick a table name that isn't used */ again: DEBUG("translating temporary table <%s> to <%s%x_%d>", tbl->pt_name, PF_OPT_TABLE_PREFIX, table_identifier, tablenum); snprintf(tbl->pt_name, sizeof(tbl->pt_name), "%s%x_%d", PF_OPT_TABLE_PREFIX, table_identifier, tablenum); PFRB_FOREACH(t, &table_buffer) { if (strcasecmp(t->pfrt_name, tbl->pt_name) == 0) { /* Collision. Try again */ DEBUG("wow, table <%s> in use. trying again", tbl->pt_name); table_identifier = arc4random(); goto again; } } tablenum++; if (pfctl_define_table(tbl->pt_name, PFR_TFLAG_CONST, 1, pf->astack[0]->name, tbl->pt_buf, pf->astack[0]->ruleset.tticket)) { warn("failed to create table %s in %s", tbl->pt_name, pf->astack[0]->name); return (1); } return (0); } /* * Partition the flat ruleset into a list of distinct superblocks */ int construct_superblocks(struct pfctl *pf, struct pf_opt_queue *opt_queue, struct superblocks *superblocks) { struct superblock *block = NULL; struct pf_opt_rule *por; int i; while (!TAILQ_EMPTY(opt_queue)) { por = TAILQ_FIRST(opt_queue); TAILQ_REMOVE(opt_queue, por, por_entry); if (block == NULL || !superblock_inclusive(block, por)) { if ((block = calloc(1, sizeof(*block))) == NULL) { warn("calloc"); return (1); } TAILQ_INIT(&block->sb_rules); for (i = 0; i < PF_SKIP_COUNT; i++) TAILQ_INIT(&block->sb_skipsteps[i]); TAILQ_INSERT_TAIL(superblocks, block, sb_entry); } TAILQ_INSERT_TAIL(&block->sb_rules, por, por_entry); } return (0); } /* * Compare two rule addresses */ int addrs_equal(struct pf_rule_addr *a, struct pf_rule_addr *b) { if (a->neg != b->neg) return (0); return (memcmp(&a->addr, &b->addr, sizeof(a->addr)) == 0); } /* * The addresses are not equal, but can we combine them into one table? */ int addrs_combineable(struct pf_rule_addr *a, struct pf_rule_addr *b) { if (a->addr.type != PF_ADDR_ADDRMASK || b->addr.type != PF_ADDR_ADDRMASK) return (0); if (a->neg != b->neg || a->port_op != b->port_op || a->port[0] != b->port[0] || a->port[1] != b->port[1]) return (0); return (1); } /* * Are we allowed to combine these two rules */ int -rules_combineable(struct pf_rule *p1, struct pf_rule *p2) +rules_combineable(struct pfctl_rule *p1, struct pfctl_rule *p2) { - struct pf_rule a, b; + struct pfctl_rule a, b; comparable_rule(&a, p1, COMBINED); comparable_rule(&b, p2, COMBINED); return (memcmp(&a, &b, sizeof(a)) == 0); } /* * Can a rule be included inside a superblock */ int superblock_inclusive(struct superblock *block, struct pf_opt_rule *por) { - struct pf_rule a, b; + struct pfctl_rule a, b; int i, j; /* First check for hard breaks */ for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++) { if (pf_rule_desc[i].prf_type == BARRIER) { for (j = 0; j < pf_rule_desc[i].prf_size; j++) if (((char *)&por->por_rule)[j + pf_rule_desc[i].prf_offset] != 0) return (0); } } /* per-rule src-track is also a hard break */ if (por->por_rule.rule_flag & PFRULE_RULESRCTRACK) return (0); /* * Have to handle interface groups separately. Consider the following * rules: * block on EXTIFS to any port 22 * pass on em0 to any port 22 * (where EXTIFS is an arbitrary interface group) * The optimizer may decide to re-order the pass rule in front of the * block rule. But what if EXTIFS includes em0??? Such a reordering * would change the meaning of the ruleset. * We can't just lookup the EXTIFS group and check if em0 is a member * because the user is allowed to add interfaces to a group during * runtime. * Ergo interface groups become a defacto superblock break :-( */ if (interface_group(por->por_rule.ifname) || interface_group(TAILQ_FIRST(&block->sb_rules)->por_rule.ifname)) { if (strcasecmp(por->por_rule.ifname, TAILQ_FIRST(&block->sb_rules)->por_rule.ifname) != 0) return (0); } comparable_rule(&a, &TAILQ_FIRST(&block->sb_rules)->por_rule, NOMERGE); comparable_rule(&b, &por->por_rule, NOMERGE); if (memcmp(&a, &b, sizeof(a)) == 0) return (1); #ifdef OPT_DEBUG for (i = 0; i < sizeof(por->por_rule); i++) { int closest = -1; if (((u_int8_t *)&a)[i] != ((u_int8_t *)&b)[i]) { for (j = 0; j < sizeof(pf_rule_desc) / sizeof(*pf_rule_desc); j++) { if (i >= pf_rule_desc[j].prf_offset && i < pf_rule_desc[j].prf_offset + pf_rule_desc[j].prf_size) { DEBUG("superblock break @ %d due to %s", por->por_rule.nr, pf_rule_desc[j].prf_name); return (0); } if (i > pf_rule_desc[j].prf_offset) { if (closest == -1 || i-pf_rule_desc[j].prf_offset < i-pf_rule_desc[closest].prf_offset) closest = j; } } if (closest >= 0) DEBUG("superblock break @ %d on %s+%xh", por->por_rule.nr, pf_rule_desc[closest].prf_name, i - pf_rule_desc[closest].prf_offset - pf_rule_desc[closest].prf_size); else DEBUG("superblock break @ %d on field @ %d", por->por_rule.nr, i); return (0); } } #endif /* OPT_DEBUG */ return (0); } /* * Figure out if an interface name is an actual interface or actually a * group of interfaces. */ int interface_group(const char *ifname) { int s; struct ifgroupreq ifgr; if (ifname == NULL || !ifname[0]) return (0); s = get_query_socket(); memset(&ifgr, 0, sizeof(ifgr)); strlcpy(ifgr.ifgr_name, ifname, IFNAMSIZ); if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) { if (errno == ENOENT) return (0); else err(1, "SIOCGIFGMEMB"); } return (1); } /* * Make a rule that can directly compared by memcmp() */ void -comparable_rule(struct pf_rule *dst, const struct pf_rule *src, int type) +comparable_rule(struct pfctl_rule *dst, const struct pfctl_rule *src, int type) { int i; /* * To simplify the comparison, we just zero out the fields that are * allowed to be different and then do a simple memcmp() */ memcpy(dst, src, sizeof(*dst)); for (i = 0; i < sizeof(pf_rule_desc)/sizeof(*pf_rule_desc); i++) if (pf_rule_desc[i].prf_type >= type) { #ifdef OPT_DEBUG assert(pf_rule_desc[i].prf_type != NEVER || *(((char *)dst) + pf_rule_desc[i].prf_offset) == 0); #endif /* OPT_DEBUG */ memset(((char *)dst) + pf_rule_desc[i].prf_offset, 0, pf_rule_desc[i].prf_size); } } /* * Remove superset information from two rules so we can directly compare them * with memcmp() */ void -exclude_supersets(struct pf_rule *super, struct pf_rule *sub) +exclude_supersets(struct pfctl_rule *super, struct pfctl_rule *sub) { if (super->ifname[0] == '\0') memset(sub->ifname, 0, sizeof(sub->ifname)); if (super->direction == PF_INOUT) sub->direction = PF_INOUT; if ((super->proto == 0 || super->proto == sub->proto) && super->flags == 0 && super->flagset == 0 && (sub->flags || sub->flagset)) { sub->flags = super->flags; sub->flagset = super->flagset; } if (super->proto == 0) sub->proto = 0; if (super->src.port_op == 0) { sub->src.port_op = 0; sub->src.port[0] = 0; sub->src.port[1] = 0; } if (super->dst.port_op == 0) { sub->dst.port_op = 0; sub->dst.port[0] = 0; sub->dst.port[1] = 0; } if (super->src.addr.type == PF_ADDR_ADDRMASK && !super->src.neg && !sub->src.neg && super->src.addr.v.a.mask.addr32[0] == 0 && super->src.addr.v.a.mask.addr32[1] == 0 && super->src.addr.v.a.mask.addr32[2] == 0 && super->src.addr.v.a.mask.addr32[3] == 0) memset(&sub->src.addr, 0, sizeof(sub->src.addr)); else if (super->src.addr.type == PF_ADDR_ADDRMASK && sub->src.addr.type == PF_ADDR_ADDRMASK && super->src.neg == sub->src.neg && super->af == sub->af && unmask(&super->src.addr.v.a.mask, super->af) < unmask(&sub->src.addr.v.a.mask, sub->af) && super->src.addr.v.a.addr.addr32[0] == (sub->src.addr.v.a.addr.addr32[0] & super->src.addr.v.a.mask.addr32[0]) && super->src.addr.v.a.addr.addr32[1] == (sub->src.addr.v.a.addr.addr32[1] & super->src.addr.v.a.mask.addr32[1]) && super->src.addr.v.a.addr.addr32[2] == (sub->src.addr.v.a.addr.addr32[2] & super->src.addr.v.a.mask.addr32[2]) && super->src.addr.v.a.addr.addr32[3] == (sub->src.addr.v.a.addr.addr32[3] & super->src.addr.v.a.mask.addr32[3])) { /* sub->src.addr is a subset of super->src.addr/mask */ memcpy(&sub->src.addr, &super->src.addr, sizeof(sub->src.addr)); } if (super->dst.addr.type == PF_ADDR_ADDRMASK && !super->dst.neg && !sub->dst.neg && super->dst.addr.v.a.mask.addr32[0] == 0 && super->dst.addr.v.a.mask.addr32[1] == 0 && super->dst.addr.v.a.mask.addr32[2] == 0 && super->dst.addr.v.a.mask.addr32[3] == 0) memset(&sub->dst.addr, 0, sizeof(sub->dst.addr)); else if (super->dst.addr.type == PF_ADDR_ADDRMASK && sub->dst.addr.type == PF_ADDR_ADDRMASK && super->dst.neg == sub->dst.neg && super->af == sub->af && unmask(&super->dst.addr.v.a.mask, super->af) < unmask(&sub->dst.addr.v.a.mask, sub->af) && super->dst.addr.v.a.addr.addr32[0] == (sub->dst.addr.v.a.addr.addr32[0] & super->dst.addr.v.a.mask.addr32[0]) && super->dst.addr.v.a.addr.addr32[1] == (sub->dst.addr.v.a.addr.addr32[1] & super->dst.addr.v.a.mask.addr32[1]) && super->dst.addr.v.a.addr.addr32[2] == (sub->dst.addr.v.a.addr.addr32[2] & super->dst.addr.v.a.mask.addr32[2]) && super->dst.addr.v.a.addr.addr32[3] == (sub->dst.addr.v.a.addr.addr32[3] & super->dst.addr.v.a.mask.addr32[3])) { /* sub->dst.addr is a subset of super->dst.addr/mask */ memcpy(&sub->dst.addr, &super->dst.addr, sizeof(sub->dst.addr)); } if (super->af == 0) sub->af = 0; } void superblock_free(struct pfctl *pf, struct superblock *block) { struct pf_opt_rule *por; while ((por = TAILQ_FIRST(&block->sb_rules))) { TAILQ_REMOVE(&block->sb_rules, por, por_entry); if (por->por_src_tbl) { if (por->por_src_tbl->pt_buf) { pfr_buf_clear(por->por_src_tbl->pt_buf); free(por->por_src_tbl->pt_buf); } free(por->por_src_tbl); } if (por->por_dst_tbl) { if (por->por_dst_tbl->pt_buf) { pfr_buf_clear(por->por_dst_tbl->pt_buf); free(por->por_dst_tbl->pt_buf); } free(por->por_dst_tbl); } free(por); } if (block->sb_profiled_block) superblock_free(pf, block->sb_profiled_block); free(block); } diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c index 7416ae26be48..b7ac9527db69 100644 --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -1,1872 +1,1872 @@ /* $OpenBSD: pfctl_parser.c,v 1.240 2008/06/10 20:55:02 mcbride Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * Copyright (c) 2002,2003 Henning Brauer * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pfctl_parser.h" #include "pfctl.h" void print_op (u_int8_t, const char *, const char *); void print_port (u_int8_t, u_int16_t, u_int16_t, const char *, int); void print_ugid (u_int8_t, unsigned, unsigned, const char *, unsigned); void print_flags (u_int8_t); void print_fromto(struct pf_rule_addr *, pf_osfp_t, struct pf_rule_addr *, u_int8_t, u_int8_t, int, int); int ifa_skip_if(const char *filter, struct node_host *p); struct node_host *host_if(const char *, int); struct node_host *host_v4(const char *, int); struct node_host *host_v6(const char *, int); struct node_host *host_dns(const char *, int, int); const char * const tcpflags = "FSRPAUEW"; static const struct icmptypeent icmp_type[] = { { "echoreq", ICMP_ECHO }, { "echorep", ICMP_ECHOREPLY }, { "unreach", ICMP_UNREACH }, { "squench", ICMP_SOURCEQUENCH }, { "redir", ICMP_REDIRECT }, { "althost", ICMP_ALTHOSTADDR }, { "routeradv", ICMP_ROUTERADVERT }, { "routersol", ICMP_ROUTERSOLICIT }, { "timex", ICMP_TIMXCEED }, { "paramprob", ICMP_PARAMPROB }, { "timereq", ICMP_TSTAMP }, { "timerep", ICMP_TSTAMPREPLY }, { "inforeq", ICMP_IREQ }, { "inforep", ICMP_IREQREPLY }, { "maskreq", ICMP_MASKREQ }, { "maskrep", ICMP_MASKREPLY }, { "trace", ICMP_TRACEROUTE }, { "dataconv", ICMP_DATACONVERR }, { "mobredir", ICMP_MOBILE_REDIRECT }, { "ipv6-where", ICMP_IPV6_WHEREAREYOU }, { "ipv6-here", ICMP_IPV6_IAMHERE }, { "mobregreq", ICMP_MOBILE_REGREQUEST }, { "mobregrep", ICMP_MOBILE_REGREPLY }, { "skip", ICMP_SKIP }, { "photuris", ICMP_PHOTURIS } }; static const struct icmptypeent icmp6_type[] = { { "unreach", ICMP6_DST_UNREACH }, { "toobig", ICMP6_PACKET_TOO_BIG }, { "timex", ICMP6_TIME_EXCEEDED }, { "paramprob", ICMP6_PARAM_PROB }, { "echoreq", ICMP6_ECHO_REQUEST }, { "echorep", ICMP6_ECHO_REPLY }, { "groupqry", ICMP6_MEMBERSHIP_QUERY }, { "listqry", MLD_LISTENER_QUERY }, { "grouprep", ICMP6_MEMBERSHIP_REPORT }, { "listenrep", MLD_LISTENER_REPORT }, { "groupterm", ICMP6_MEMBERSHIP_REDUCTION }, { "listendone", MLD_LISTENER_DONE }, { "routersol", ND_ROUTER_SOLICIT }, { "routeradv", ND_ROUTER_ADVERT }, { "neighbrsol", ND_NEIGHBOR_SOLICIT }, { "neighbradv", ND_NEIGHBOR_ADVERT }, { "redir", ND_REDIRECT }, { "routrrenum", ICMP6_ROUTER_RENUMBERING }, { "wrureq", ICMP6_WRUREQUEST }, { "wrurep", ICMP6_WRUREPLY }, { "fqdnreq", ICMP6_FQDN_QUERY }, { "fqdnrep", ICMP6_FQDN_REPLY }, { "niqry", ICMP6_NI_QUERY }, { "nirep", ICMP6_NI_REPLY }, { "mtraceresp", MLD_MTRACE_RESP }, { "mtrace", MLD_MTRACE } }; static const struct icmpcodeent icmp_code[] = { { "net-unr", ICMP_UNREACH, ICMP_UNREACH_NET }, { "host-unr", ICMP_UNREACH, ICMP_UNREACH_HOST }, { "proto-unr", ICMP_UNREACH, ICMP_UNREACH_PROTOCOL }, { "port-unr", ICMP_UNREACH, ICMP_UNREACH_PORT }, { "needfrag", ICMP_UNREACH, ICMP_UNREACH_NEEDFRAG }, { "srcfail", ICMP_UNREACH, ICMP_UNREACH_SRCFAIL }, { "net-unk", ICMP_UNREACH, ICMP_UNREACH_NET_UNKNOWN }, { "host-unk", ICMP_UNREACH, ICMP_UNREACH_HOST_UNKNOWN }, { "isolate", ICMP_UNREACH, ICMP_UNREACH_ISOLATED }, { "net-prohib", ICMP_UNREACH, ICMP_UNREACH_NET_PROHIB }, { "host-prohib", ICMP_UNREACH, ICMP_UNREACH_HOST_PROHIB }, { "net-tos", ICMP_UNREACH, ICMP_UNREACH_TOSNET }, { "host-tos", ICMP_UNREACH, ICMP_UNREACH_TOSHOST }, { "filter-prohib", ICMP_UNREACH, ICMP_UNREACH_FILTER_PROHIB }, { "host-preced", ICMP_UNREACH, ICMP_UNREACH_HOST_PRECEDENCE }, { "cutoff-preced", ICMP_UNREACH, ICMP_UNREACH_PRECEDENCE_CUTOFF }, { "redir-net", ICMP_REDIRECT, ICMP_REDIRECT_NET }, { "redir-host", ICMP_REDIRECT, ICMP_REDIRECT_HOST }, { "redir-tos-net", ICMP_REDIRECT, ICMP_REDIRECT_TOSNET }, { "redir-tos-host", ICMP_REDIRECT, ICMP_REDIRECT_TOSHOST }, { "normal-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NORMAL }, { "common-adv", ICMP_ROUTERADVERT, ICMP_ROUTERADVERT_NOROUTE_COMMON }, { "transit", ICMP_TIMXCEED, ICMP_TIMXCEED_INTRANS }, { "reassemb", ICMP_TIMXCEED, ICMP_TIMXCEED_REASS }, { "badhead", ICMP_PARAMPROB, ICMP_PARAMPROB_ERRATPTR }, { "optmiss", ICMP_PARAMPROB, ICMP_PARAMPROB_OPTABSENT }, { "badlen", ICMP_PARAMPROB, ICMP_PARAMPROB_LENGTH }, { "unknown-ind", ICMP_PHOTURIS, ICMP_PHOTURIS_UNKNOWN_INDEX }, { "auth-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_AUTH_FAILED }, { "decrypt-fail", ICMP_PHOTURIS, ICMP_PHOTURIS_DECRYPT_FAILED } }; static const struct icmpcodeent icmp6_code[] = { { "admin-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADMIN }, { "noroute-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOROUTE }, { "notnbr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOTNEIGHBOR }, { "beyond-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_BEYONDSCOPE }, { "addr-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR }, { "port-unr", ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_NOPORT }, { "transit", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_TRANSIT }, { "reassemb", ICMP6_TIME_EXCEEDED, ICMP6_TIME_EXCEED_REASSEMBLY }, { "badhead", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_HEADER }, { "nxthdr", ICMP6_PARAM_PROB, ICMP6_PARAMPROB_NEXTHEADER }, { "redironlink", ND_REDIRECT, ND_REDIRECT_ONLINK }, { "redirrouter", ND_REDIRECT, ND_REDIRECT_ROUTER } }; const struct pf_timeout pf_timeouts[] = { { "tcp.first", PFTM_TCP_FIRST_PACKET }, { "tcp.opening", PFTM_TCP_OPENING }, { "tcp.established", PFTM_TCP_ESTABLISHED }, { "tcp.closing", PFTM_TCP_CLOSING }, { "tcp.finwait", PFTM_TCP_FIN_WAIT }, { "tcp.closed", PFTM_TCP_CLOSED }, { "tcp.tsdiff", PFTM_TS_DIFF }, { "udp.first", PFTM_UDP_FIRST_PACKET }, { "udp.single", PFTM_UDP_SINGLE }, { "udp.multiple", PFTM_UDP_MULTIPLE }, { "icmp.first", PFTM_ICMP_FIRST_PACKET }, { "icmp.error", PFTM_ICMP_ERROR_REPLY }, { "other.first", PFTM_OTHER_FIRST_PACKET }, { "other.single", PFTM_OTHER_SINGLE }, { "other.multiple", PFTM_OTHER_MULTIPLE }, { "frag", PFTM_FRAG }, { "interval", PFTM_INTERVAL }, { "adaptive.start", PFTM_ADAPTIVE_START }, { "adaptive.end", PFTM_ADAPTIVE_END }, { "src.track", PFTM_SRC_NODE }, { NULL, 0 } }; static struct hsearch_data isgroup_map; static __attribute__((constructor)) void pfctl_parser_init(void) { /* * As hdestroy() will never be called on these tables, it will be * safe to use references into the stored data as keys. */ if (hcreate_r(0, &isgroup_map) == 0) err(1, "Failed to create interface group query response map"); } const struct icmptypeent * geticmptypebynumber(u_int8_t type, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < nitems(icmp_type); i++) { if (type == icmp_type[i].type) return (&icmp_type[i]); } } else { for (i=0; i < nitems(icmp6_type); i++) { if (type == icmp6_type[i].type) return (&icmp6_type[i]); } } return (NULL); } const struct icmptypeent * geticmptypebyname(char *w, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < nitems(icmp_type); i++) { if (!strcmp(w, icmp_type[i].name)) return (&icmp_type[i]); } } else { for (i=0; i < nitems(icmp6_type); i++) { if (!strcmp(w, icmp6_type[i].name)) return (&icmp6_type[i]); } } return (NULL); } const struct icmpcodeent * geticmpcodebynumber(u_int8_t type, u_int8_t code, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < nitems(icmp_code); i++) { if (type == icmp_code[i].type && code == icmp_code[i].code) return (&icmp_code[i]); } } else { for (i=0; i < nitems(icmp6_code); i++) { if (type == icmp6_code[i].type && code == icmp6_code[i].code) return (&icmp6_code[i]); } } return (NULL); } const struct icmpcodeent * geticmpcodebyname(u_long type, char *w, sa_family_t af) { unsigned int i; if (af != AF_INET6) { for (i=0; i < nitems(icmp_code); i++) { if (type == icmp_code[i].type && !strcmp(w, icmp_code[i].name)) return (&icmp_code[i]); } } else { for (i=0; i < nitems(icmp6_code); i++) { if (type == icmp6_code[i].type && !strcmp(w, icmp6_code[i].name)) return (&icmp6_code[i]); } } return (NULL); } void print_op(u_int8_t op, const char *a1, const char *a2) { if (op == PF_OP_IRG) printf(" %s >< %s", a1, a2); else if (op == PF_OP_XRG) printf(" %s <> %s", a1, a2); else if (op == PF_OP_EQ) printf(" = %s", a1); else if (op == PF_OP_NE) printf(" != %s", a1); else if (op == PF_OP_LT) printf(" < %s", a1); else if (op == PF_OP_LE) printf(" <= %s", a1); else if (op == PF_OP_GT) printf(" > %s", a1); else if (op == PF_OP_GE) printf(" >= %s", a1); else if (op == PF_OP_RRG) printf(" %s:%s", a1, a2); } void print_port(u_int8_t op, u_int16_t p1, u_int16_t p2, const char *proto, int numeric) { char a1[6], a2[6]; struct servent *s; if (!numeric) s = getservbyport(p1, proto); else s = NULL; p1 = ntohs(p1); p2 = ntohs(p2); snprintf(a1, sizeof(a1), "%u", p1); snprintf(a2, sizeof(a2), "%u", p2); printf(" port"); if (s != NULL && (op == PF_OP_EQ || op == PF_OP_NE)) print_op(op, s->s_name, a2); else print_op(op, a1, a2); } void print_ugid(u_int8_t op, unsigned u1, unsigned u2, const char *t, unsigned umax) { char a1[11], a2[11]; snprintf(a1, sizeof(a1), "%u", u1); snprintf(a2, sizeof(a2), "%u", u2); printf(" %s", t); if (u1 == umax && (op == PF_OP_EQ || op == PF_OP_NE)) print_op(op, "unknown", a2); else print_op(op, a1, a2); } void print_flags(u_int8_t f) { int i; for (i = 0; tcpflags[i]; ++i) if (f & (1 << i)) printf("%c", tcpflags[i]); } void print_fromto(struct pf_rule_addr *src, pf_osfp_t osfp, struct pf_rule_addr *dst, sa_family_t af, u_int8_t proto, int verbose, int numeric) { char buf[PF_OSFP_LEN*3]; if (src->addr.type == PF_ADDR_ADDRMASK && dst->addr.type == PF_ADDR_ADDRMASK && PF_AZERO(&src->addr.v.a.addr, AF_INET6) && PF_AZERO(&src->addr.v.a.mask, AF_INET6) && PF_AZERO(&dst->addr.v.a.addr, AF_INET6) && PF_AZERO(&dst->addr.v.a.mask, AF_INET6) && !src->neg && !dst->neg && !src->port_op && !dst->port_op && osfp == PF_OSFP_ANY) printf(" all"); else { printf(" from "); if (src->neg) printf("! "); print_addr(&src->addr, af, verbose); if (src->port_op) print_port(src->port_op, src->port[0], src->port[1], proto == IPPROTO_TCP ? "tcp" : "udp", numeric); if (osfp != PF_OSFP_ANY) printf(" os \"%s\"", pfctl_lookup_fingerprint(osfp, buf, sizeof(buf))); printf(" to "); if (dst->neg) printf("! "); print_addr(&dst->addr, af, verbose); if (dst->port_op) print_port(dst->port_op, dst->port[0], dst->port[1], proto == IPPROTO_TCP ? "tcp" : "udp", numeric); } } void print_pool(struct pf_pool *pool, u_int16_t p1, u_int16_t p2, sa_family_t af, int id) { struct pf_pooladdr *pooladdr; if ((TAILQ_FIRST(&pool->list) != NULL) && TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) printf("{ "); TAILQ_FOREACH(pooladdr, &pool->list, entries){ switch (id) { case PF_NAT: case PF_RDR: case PF_BINAT: print_addr(&pooladdr->addr, af, 0); break; case PF_PASS: if (PF_AZERO(&pooladdr->addr.v.a.addr, af)) printf("%s", pooladdr->ifname); else { printf("(%s ", pooladdr->ifname); print_addr(&pooladdr->addr, af, 0); printf(")"); } break; default: break; } if (TAILQ_NEXT(pooladdr, entries) != NULL) printf(", "); else if (TAILQ_NEXT(TAILQ_FIRST(&pool->list), entries) != NULL) printf(" }"); } switch (id) { case PF_NAT: if ((p1 != PF_NAT_PROXY_PORT_LOW || p2 != PF_NAT_PROXY_PORT_HIGH) && (p1 != 0 || p2 != 0)) { if (p1 == p2) printf(" port %u", p1); else printf(" port %u:%u", p1, p2); } break; case PF_RDR: if (p1) { printf(" port %u", p1); if (p2 && (p2 != p1)) printf(":%u", p2); } break; default: break; } switch (pool->opts & PF_POOL_TYPEMASK) { case PF_POOL_NONE: break; case PF_POOL_BITMASK: printf(" bitmask"); break; case PF_POOL_RANDOM: printf(" random"); break; case PF_POOL_SRCHASH: printf(" source-hash 0x%08x%08x%08x%08x", pool->key.key32[0], pool->key.key32[1], pool->key.key32[2], pool->key.key32[3]); break; case PF_POOL_ROUNDROBIN: printf(" round-robin"); break; } if (pool->opts & PF_POOL_STICKYADDR) printf(" sticky-address"); if (id == PF_NAT && p1 == 0 && p2 == 0) printf(" static-port"); } const char * const pf_reasons[PFRES_MAX+1] = PFRES_NAMES; const char * const pf_lcounters[LCNT_MAX+1] = LCNT_NAMES; const char * const pf_fcounters[FCNT_MAX+1] = FCNT_NAMES; const char * const pf_scounters[FCNT_MAX+1] = FCNT_NAMES; void print_status(struct pf_status *s, int opts) { char statline[80], *running; time_t runtime; int i; char buf[PF_MD5_DIGEST_LENGTH * 2 + 1]; static const char hex[] = "0123456789abcdef"; runtime = time(NULL) - s->since; running = s->running ? "Enabled" : "Disabled"; if (s->since) { unsigned int sec, min, hrs, day = runtime; sec = day % 60; day /= 60; min = day % 60; day /= 60; hrs = day % 24; day /= 24; snprintf(statline, sizeof(statline), "Status: %s for %u days %.2u:%.2u:%.2u", running, day, hrs, min, sec); } else snprintf(statline, sizeof(statline), "Status: %s", running); printf("%-44s", statline); switch (s->debug) { case PF_DEBUG_NONE: printf("%15s\n\n", "Debug: None"); break; case PF_DEBUG_URGENT: printf("%15s\n\n", "Debug: Urgent"); break; case PF_DEBUG_MISC: printf("%15s\n\n", "Debug: Misc"); break; case PF_DEBUG_NOISY: printf("%15s\n\n", "Debug: Loud"); break; } if (opts & PF_OPT_VERBOSE) { printf("Hostid: 0x%08x\n", ntohl(s->hostid)); for (i = 0; i < PF_MD5_DIGEST_LENGTH; i++) { buf[i + i] = hex[s->pf_chksum[i] >> 4]; buf[i + i + 1] = hex[s->pf_chksum[i] & 0x0f]; } buf[i + i] = '\0'; printf("Checksum: 0x%s\n\n", buf); } if (s->ifname[0] != 0) { printf("Interface Stats for %-16s %5s %16s\n", s->ifname, "IPv4", "IPv6"); printf(" %-25s %14llu %16llu\n", "Bytes In", (unsigned long long)s->bcounters[0][0], (unsigned long long)s->bcounters[1][0]); printf(" %-25s %14llu %16llu\n", "Bytes Out", (unsigned long long)s->bcounters[0][1], (unsigned long long)s->bcounters[1][1]); printf(" Packets In\n"); printf(" %-23s %14llu %16llu\n", "Passed", (unsigned long long)s->pcounters[0][0][PF_PASS], (unsigned long long)s->pcounters[1][0][PF_PASS]); printf(" %-23s %14llu %16llu\n", "Blocked", (unsigned long long)s->pcounters[0][0][PF_DROP], (unsigned long long)s->pcounters[1][0][PF_DROP]); printf(" Packets Out\n"); printf(" %-23s %14llu %16llu\n", "Passed", (unsigned long long)s->pcounters[0][1][PF_PASS], (unsigned long long)s->pcounters[1][1][PF_PASS]); printf(" %-23s %14llu %16llu\n\n", "Blocked", (unsigned long long)s->pcounters[0][1][PF_DROP], (unsigned long long)s->pcounters[1][1][PF_DROP]); } printf("%-27s %14s %16s\n", "State Table", "Total", "Rate"); printf(" %-25s %14u %14s\n", "current entries", s->states, ""); for (i = 0; i < FCNT_MAX; i++) { printf(" %-25s %14llu ", pf_fcounters[i], (unsigned long long)s->fcounters[i]); if (runtime > 0) printf("%14.1f/s\n", (double)s->fcounters[i] / (double)runtime); else printf("%14s\n", ""); } if (opts & PF_OPT_VERBOSE) { printf("Source Tracking Table\n"); printf(" %-25s %14u %14s\n", "current entries", s->src_nodes, ""); for (i = 0; i < SCNT_MAX; i++) { printf(" %-25s %14lld ", pf_scounters[i], #ifdef __FreeBSD__ (long long)s->scounters[i]); #else s->scounters[i]); #endif if (runtime > 0) printf("%14.1f/s\n", (double)s->scounters[i] / (double)runtime); else printf("%14s\n", ""); } } printf("Counters\n"); for (i = 0; i < PFRES_MAX; i++) { printf(" %-25s %14llu ", pf_reasons[i], (unsigned long long)s->counters[i]); if (runtime > 0) printf("%14.1f/s\n", (double)s->counters[i] / (double)runtime); else printf("%14s\n", ""); } if (opts & PF_OPT_VERBOSE) { printf("Limit Counters\n"); for (i = 0; i < LCNT_MAX; i++) { printf(" %-25s %14lld ", pf_lcounters[i], #ifdef __FreeBSD__ (unsigned long long)s->lcounters[i]); #else s->lcounters[i]); #endif if (runtime > 0) printf("%14.1f/s\n", (double)s->lcounters[i] / (double)runtime); else printf("%14s\n", ""); } } } void print_running(struct pf_status *status) { printf("%s\n", status->running ? "Enabled" : "Disabled"); } void print_src_node(struct pf_src_node *sn, int opts) { struct pf_addr_wrap aw; int min, sec; memset(&aw, 0, sizeof(aw)); if (sn->af == AF_INET) aw.v.a.mask.addr32[0] = 0xffffffff; else memset(&aw.v.a.mask, 0xff, sizeof(aw.v.a.mask)); aw.v.a.addr = sn->addr; print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2); printf(" -> "); aw.v.a.addr = sn->raddr; print_addr(&aw, sn->af, opts & PF_OPT_VERBOSE2); printf(" ( states %u, connections %u, rate %u.%u/%us )\n", sn->states, sn->conn, sn->conn_rate.count / 1000, (sn->conn_rate.count % 1000) / 100, sn->conn_rate.seconds); if (opts & PF_OPT_VERBOSE) { sec = sn->creation % 60; sn->creation /= 60; min = sn->creation % 60; sn->creation /= 60; printf(" age %.2u:%.2u:%.2u", sn->creation, min, sec); if (sn->states == 0) { sec = sn->expire % 60; sn->expire /= 60; min = sn->expire % 60; sn->expire /= 60; printf(", expires in %.2u:%.2u:%.2u", sn->expire, min, sec); } printf(", %llu pkts, %llu bytes", #ifdef __FreeBSD__ (unsigned long long)(sn->packets[0] + sn->packets[1]), (unsigned long long)(sn->bytes[0] + sn->bytes[1])); #else sn->packets[0] + sn->packets[1], sn->bytes[0] + sn->bytes[1]); #endif switch (sn->ruletype) { case PF_NAT: if (sn->rule.nr != -1) printf(", nat rule %u", sn->rule.nr); break; case PF_RDR: if (sn->rule.nr != -1) printf(", rdr rule %u", sn->rule.nr); break; case PF_PASS: if (sn->rule.nr != -1) printf(", filter rule %u", sn->rule.nr); break; } printf("\n"); } } void -print_rule(struct pf_rule *r, const char *anchor_call, int verbose, int numeric) +print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numeric) { static const char *actiontypes[] = { "pass", "block", "scrub", "no scrub", "nat", "no nat", "binat", "no binat", "rdr", "no rdr" }; static const char *anchortypes[] = { "anchor", "anchor", "anchor", "anchor", "nat-anchor", "nat-anchor", "binat-anchor", "binat-anchor", "rdr-anchor", "rdr-anchor" }; int i, opts; if (verbose) printf("@%d ", r->nr); if (r->action > PF_NORDR) printf("action(%d)", r->action); else if (anchor_call[0]) { if (anchor_call[0] == '_') { printf("%s", anchortypes[r->action]); } else printf("%s \"%s\"", anchortypes[r->action], anchor_call); } else { printf("%s", actiontypes[r->action]); if (r->natpass) printf(" pass"); } if (r->action == PF_DROP) { if (r->rule_flag & PFRULE_RETURN) printf(" return"); else if (r->rule_flag & PFRULE_RETURNRST) { if (!r->return_ttl) printf(" return-rst"); else printf(" return-rst(ttl %d)", r->return_ttl); } else if (r->rule_flag & PFRULE_RETURNICMP) { const struct icmpcodeent *ic, *ic6; ic = geticmpcodebynumber(r->return_icmp >> 8, r->return_icmp & 255, AF_INET); ic6 = geticmpcodebynumber(r->return_icmp6 >> 8, r->return_icmp6 & 255, AF_INET6); switch (r->af) { case AF_INET: printf(" return-icmp"); if (ic == NULL) printf("(%u)", r->return_icmp & 255); else printf("(%s)", ic->name); break; case AF_INET6: printf(" return-icmp6"); if (ic6 == NULL) printf("(%u)", r->return_icmp6 & 255); else printf("(%s)", ic6->name); break; default: printf(" return-icmp"); if (ic == NULL) printf("(%u, ", r->return_icmp & 255); else printf("(%s, ", ic->name); if (ic6 == NULL) printf("%u)", r->return_icmp6 & 255); else printf("%s)", ic6->name); break; } } else printf(" drop"); } if (r->direction == PF_IN) printf(" in"); else if (r->direction == PF_OUT) printf(" out"); if (r->log) { printf(" log"); if (r->log & ~PF_LOG || r->logif) { int count = 0; printf(" ("); if (r->log & PF_LOG_ALL) printf("%sall", count++ ? ", " : ""); if (r->log & PF_LOG_SOCKET_LOOKUP) printf("%suser", count++ ? ", " : ""); if (r->logif) printf("%sto pflog%u", count++ ? ", " : "", r->logif); printf(")"); } } if (r->quick) printf(" quick"); if (r->ifname[0]) { if (r->ifnot) printf(" on ! %s", r->ifname); else printf(" on %s", r->ifname); } if (r->rt) { if (r->rt == PF_ROUTETO) printf(" route-to"); else if (r->rt == PF_REPLYTO) printf(" reply-to"); else if (r->rt == PF_DUPTO) printf(" dup-to"); printf(" "); print_pool(&r->rpool, 0, 0, r->af, PF_PASS); } if (r->af) { if (r->af == AF_INET) printf(" inet"); else printf(" inet6"); } if (r->proto) { struct protoent *p; if ((p = getprotobynumber(r->proto)) != NULL) printf(" proto %s", p->p_name); else printf(" proto %u", r->proto); } print_fromto(&r->src, r->os_fingerprint, &r->dst, r->af, r->proto, verbose, numeric); if (r->uid.op) print_ugid(r->uid.op, r->uid.uid[0], r->uid.uid[1], "user", UID_MAX); if (r->gid.op) print_ugid(r->gid.op, r->gid.gid[0], r->gid.gid[1], "group", GID_MAX); if (r->flags || r->flagset) { printf(" flags "); print_flags(r->flags); printf("/"); print_flags(r->flagset); } else if (r->action == PF_PASS && (!r->proto || r->proto == IPPROTO_TCP) && !(r->rule_flag & PFRULE_FRAGMENT) && !anchor_call[0] && r->keep_state) printf(" flags any"); if (r->type) { const struct icmptypeent *it; it = geticmptypebynumber(r->type-1, r->af); if (r->af != AF_INET6) printf(" icmp-type"); else printf(" icmp6-type"); if (it != NULL) printf(" %s", it->name); else printf(" %u", r->type-1); if (r->code) { const struct icmpcodeent *ic; ic = geticmpcodebynumber(r->type-1, r->code-1, r->af); if (ic != NULL) printf(" code %s", ic->name); else printf(" code %u", r->code-1); } } if (r->tos) printf(" tos 0x%2.2x", r->tos); if (r->prio) printf(" prio %u", r->prio == PF_PRIO_ZERO ? 0 : r->prio); if (r->scrub_flags & PFSTATE_SETMASK) { char *comma = ""; printf(" set ("); if (r->scrub_flags & PFSTATE_SETPRIO) { if (r->set_prio[0] == r->set_prio[1]) printf("%s prio %u", comma, r->set_prio[0]); else printf("%s prio(%u, %u)", comma, r->set_prio[0], r->set_prio[1]); comma = ","; } printf(" )"); } if (!r->keep_state && r->action == PF_PASS && !anchor_call[0]) printf(" no state"); else if (r->keep_state == PF_STATE_NORMAL) printf(" keep state"); else if (r->keep_state == PF_STATE_MODULATE) printf(" modulate state"); else if (r->keep_state == PF_STATE_SYNPROXY) printf(" synproxy state"); if (r->prob) { char buf[20]; snprintf(buf, sizeof(buf), "%f", r->prob*100.0/(UINT_MAX+1.0)); for (i = strlen(buf)-1; i > 0; i--) { if (buf[i] == '0') buf[i] = '\0'; else { if (buf[i] == '.') buf[i] = '\0'; break; } } printf(" probability %s%%", buf); } opts = 0; if (r->max_states || r->max_src_nodes || r->max_src_states) opts = 1; if (r->rule_flag & PFRULE_NOSYNC) opts = 1; if (r->rule_flag & PFRULE_SRCTRACK) opts = 1; if (r->rule_flag & PFRULE_IFBOUND) opts = 1; if (r->rule_flag & PFRULE_STATESLOPPY) opts = 1; for (i = 0; !opts && i < PFTM_MAX; ++i) if (r->timeout[i]) opts = 1; if (opts) { printf(" ("); if (r->max_states) { printf("max %u", r->max_states); opts = 0; } if (r->rule_flag & PFRULE_NOSYNC) { if (!opts) printf(", "); printf("no-sync"); opts = 0; } if (r->rule_flag & PFRULE_SRCTRACK) { if (!opts) printf(", "); printf("source-track"); if (r->rule_flag & PFRULE_RULESRCTRACK) printf(" rule"); else printf(" global"); opts = 0; } if (r->max_src_states) { if (!opts) printf(", "); printf("max-src-states %u", r->max_src_states); opts = 0; } if (r->max_src_conn) { if (!opts) printf(", "); printf("max-src-conn %u", r->max_src_conn); opts = 0; } if (r->max_src_conn_rate.limit) { if (!opts) printf(", "); printf("max-src-conn-rate %u/%u", r->max_src_conn_rate.limit, r->max_src_conn_rate.seconds); opts = 0; } if (r->max_src_nodes) { if (!opts) printf(", "); printf("max-src-nodes %u", r->max_src_nodes); opts = 0; } if (r->overload_tblname[0]) { if (!opts) printf(", "); printf("overload <%s>", r->overload_tblname); if (r->flush) printf(" flush"); if (r->flush & PF_FLUSH_GLOBAL) printf(" global"); } if (r->rule_flag & PFRULE_IFBOUND) { if (!opts) printf(", "); printf("if-bound"); opts = 0; } if (r->rule_flag & PFRULE_STATESLOPPY) { if (!opts) printf(", "); printf("sloppy"); opts = 0; } for (i = 0; i < PFTM_MAX; ++i) if (r->timeout[i]) { int j; if (!opts) printf(", "); opts = 0; for (j = 0; pf_timeouts[j].name != NULL; ++j) if (pf_timeouts[j].timeout == i) break; printf("%s %u", pf_timeouts[j].name == NULL ? "inv.timeout" : pf_timeouts[j].name, r->timeout[i]); } printf(")"); } if (r->rule_flag & PFRULE_FRAGMENT) printf(" fragment"); if (r->rule_flag & PFRULE_NODF) printf(" no-df"); if (r->rule_flag & PFRULE_RANDOMID) printf(" random-id"); if (r->min_ttl) printf(" min-ttl %d", r->min_ttl); if (r->max_mss) printf(" max-mss %d", r->max_mss); if (r->rule_flag & PFRULE_SET_TOS) printf(" set-tos 0x%2.2x", r->set_tos); if (r->allow_opts) printf(" allow-opts"); if (r->action == PF_SCRUB) { if (r->rule_flag & PFRULE_REASSEMBLE_TCP) printf(" reassemble tcp"); printf(" fragment reassemble"); } if (r->label[0]) printf(" label \"%s\"", r->label); if (r->qname[0] && r->pqname[0]) printf(" queue(%s, %s)", r->qname, r->pqname); else if (r->qname[0]) printf(" queue %s", r->qname); if (r->tagname[0]) printf(" tag %s", r->tagname); if (r->match_tagname[0]) { if (r->match_tag_not) printf(" !"); printf(" tagged %s", r->match_tagname); } if (r->rtableid != -1) printf(" rtable %u", r->rtableid); if (r->divert.port) { #ifdef __FreeBSD__ printf(" divert-to %u", ntohs(r->divert.port)); #else if (PF_AZERO(&r->divert.addr, r->af)) { printf(" divert-reply"); } else { /* XXX cut&paste from print_addr */ char buf[48]; printf(" divert-to "); if (inet_ntop(r->af, &r->divert.addr, buf, sizeof(buf)) == NULL) printf("?"); else printf("%s", buf); printf(" port %u", ntohs(r->divert.port)); } #endif } if (!anchor_call[0] && (r->action == PF_NAT || r->action == PF_BINAT || r->action == PF_RDR)) { printf(" -> "); print_pool(&r->rpool, r->rpool.proxy_port[0], r->rpool.proxy_port[1], r->af, r->action); } } void print_tabledef(const char *name, int flags, int addrs, struct node_tinithead *nodes) { struct node_tinit *ti, *nti; struct node_host *h; printf("table <%s>", name); if (flags & PFR_TFLAG_CONST) printf(" const"); if (flags & PFR_TFLAG_PERSIST) printf(" persist"); if (flags & PFR_TFLAG_COUNTERS) printf(" counters"); SIMPLEQ_FOREACH(ti, nodes, entries) { if (ti->file) { printf(" file \"%s\"", ti->file); continue; } printf(" {"); for (;;) { for (h = ti->host; h != NULL; h = h->next) { printf(h->not ? " !" : " "); print_addr(&h->addr, h->af, 0); } nti = SIMPLEQ_NEXT(ti, entries); if (nti != NULL && nti->file == NULL) ti = nti; /* merge lists */ else break; } printf(" }"); } if (addrs && SIMPLEQ_EMPTY(nodes)) printf(" { }"); printf("\n"); } int parse_flags(char *s) { char *p, *q; u_int8_t f = 0; for (p = s; *p; p++) { if ((q = strchr(tcpflags, *p)) == NULL) return -1; else f |= 1 << (q - tcpflags); } return (f ? f : PF_TH_ALL); } void set_ipmask(struct node_host *h, u_int8_t b) { struct pf_addr *m, *n; int i, j = 0; m = &h->addr.v.a.mask; memset(m, 0, sizeof(*m)); while (b >= 32) { m->addr32[j++] = 0xffffffff; b -= 32; } for (i = 31; i > 31-b; --i) m->addr32[j] |= (1 << i); if (b) m->addr32[j] = htonl(m->addr32[j]); /* Mask off bits of the address that will never be used. */ n = &h->addr.v.a.addr; if (h->addr.type == PF_ADDR_ADDRMASK) for (i = 0; i < 4; i++) n->addr32[i] = n->addr32[i] & m->addr32[i]; } int check_netmask(struct node_host *h, sa_family_t af) { struct node_host *n = NULL; struct pf_addr *m; for (n = h; n != NULL; n = n->next) { if (h->addr.type == PF_ADDR_TABLE) continue; m = &h->addr.v.a.mask; /* fix up netmask for dynaddr */ if (af == AF_INET && h->addr.type == PF_ADDR_DYNIFTL && unmask(m, AF_INET6) > 32) set_ipmask(n, 32); /* netmasks > 32 bit are invalid on v4 */ if (af == AF_INET && (m->addr32[1] || m->addr32[2] || m->addr32[3])) { fprintf(stderr, "netmask %u invalid for IPv4 address\n", unmask(m, AF_INET6)); return (1); } } return (0); } /* interface lookup routines */ static struct node_host *iftab; /* * Retrieve the list of groups this interface is a member of and make sure * each group is in the group map. */ static void ifa_add_groups_to_map(char *ifa_name) { int s, len; struct ifgroupreq ifgr; struct ifg_req *ifg; s = get_query_socket(); /* Get size of group list for this interface */ memset(&ifgr, 0, sizeof(ifgr)); strlcpy(ifgr.ifgr_name, ifa_name, IFNAMSIZ); if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) err(1, "SIOCGIFGROUP"); /* Retrieve group list for this interface */ len = ifgr.ifgr_len; ifgr.ifgr_groups = (struct ifg_req *)calloc(len / sizeof(struct ifg_req), sizeof(struct ifg_req)); if (ifgr.ifgr_groups == NULL) err(1, "calloc"); if (ioctl(s, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) err(1, "SIOCGIFGROUP"); ifg = ifgr.ifgr_groups; for (; ifg && len >= sizeof(struct ifg_req); ifg++) { len -= sizeof(struct ifg_req); if (strcmp(ifg->ifgrq_group, "all")) { ENTRY item; ENTRY *ret_item; int *answer; item.key = ifg->ifgrq_group; if (hsearch_r(item, FIND, &ret_item, &isgroup_map) == 0) { struct ifgroupreq ifgr2; /* Don't know the answer yet */ if ((answer = malloc(sizeof(int))) == NULL) err(1, "malloc"); bzero(&ifgr2, sizeof(ifgr2)); strlcpy(ifgr2.ifgr_name, ifg->ifgrq_group, sizeof(ifgr2.ifgr_name)); if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr2) == 0) *answer = ifgr2.ifgr_len; else *answer = 0; item.key = strdup(ifg->ifgrq_group); item.data = answer; if (hsearch_r(item, ENTER, &ret_item, &isgroup_map) == 0) err(1, "interface group query response" " map insert"); } } } free(ifgr.ifgr_groups); } void ifa_load(void) { struct ifaddrs *ifap, *ifa; struct node_host *n = NULL, *h = NULL; if (getifaddrs(&ifap) < 0) err(1, "getifaddrs"); for (ifa = ifap; ifa; ifa = ifa->ifa_next) { if (!(ifa->ifa_addr->sa_family == AF_INET || ifa->ifa_addr->sa_family == AF_INET6 || ifa->ifa_addr->sa_family == AF_LINK)) continue; n = calloc(1, sizeof(struct node_host)); if (n == NULL) err(1, "address: calloc"); n->af = ifa->ifa_addr->sa_family; n->ifa_flags = ifa->ifa_flags; #ifdef __KAME__ if (n->af == AF_INET6 && IN6_IS_ADDR_LINKLOCAL(&((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr) && ((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_scope_id == 0) { struct sockaddr_in6 *sin6; sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; sin6->sin6_scope_id = sin6->sin6_addr.s6_addr[2] << 8 | sin6->sin6_addr.s6_addr[3]; sin6->sin6_addr.s6_addr[2] = 0; sin6->sin6_addr.s6_addr[3] = 0; } #endif n->ifindex = 0; if (n->af == AF_INET) { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in *) ifa->ifa_addr)->sin_addr.s_addr, sizeof(struct in_addr)); memcpy(&n->addr.v.a.mask, &((struct sockaddr_in *) ifa->ifa_netmask)->sin_addr.s_addr, sizeof(struct in_addr)); if (ifa->ifa_broadaddr != NULL) memcpy(&n->bcast, &((struct sockaddr_in *) ifa->ifa_broadaddr)->sin_addr.s_addr, sizeof(struct in_addr)); if (ifa->ifa_dstaddr != NULL) memcpy(&n->peer, &((struct sockaddr_in *) ifa->ifa_dstaddr)->sin_addr.s_addr, sizeof(struct in_addr)); } else if (n->af == AF_INET6) { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_addr.s6_addr, sizeof(struct in6_addr)); memcpy(&n->addr.v.a.mask, &((struct sockaddr_in6 *) ifa->ifa_netmask)->sin6_addr.s6_addr, sizeof(struct in6_addr)); if (ifa->ifa_broadaddr != NULL) memcpy(&n->bcast, &((struct sockaddr_in6 *) ifa->ifa_broadaddr)->sin6_addr.s6_addr, sizeof(struct in6_addr)); if (ifa->ifa_dstaddr != NULL) memcpy(&n->peer, &((struct sockaddr_in6 *) ifa->ifa_dstaddr)->sin6_addr.s6_addr, sizeof(struct in6_addr)); n->ifindex = ((struct sockaddr_in6 *) ifa->ifa_addr)->sin6_scope_id; } else if (n->af == AF_LINK) { ifa_add_groups_to_map(ifa->ifa_name); } if ((n->ifname = strdup(ifa->ifa_name)) == NULL) err(1, "ifa_load: strdup"); n->next = NULL; n->tail = n; if (h == NULL) h = n; else { h->tail->next = n; h->tail = n; } } iftab = h; freeifaddrs(ifap); } static int get_socket_domain(void) { int sdom; sdom = AF_UNSPEC; #ifdef WITH_INET6 if (sdom == AF_UNSPEC && feature_present("inet6")) sdom = AF_INET6; #endif #ifdef WITH_INET if (sdom == AF_UNSPEC && feature_present("inet")) sdom = AF_INET; #endif if (sdom == AF_UNSPEC) sdom = AF_LINK; return (sdom); } int get_query_socket(void) { static int s = -1; if (s == -1) { if ((s = socket(get_socket_domain(), SOCK_DGRAM, 0)) == -1) err(1, "socket"); } return (s); } /* * Returns the response len if the name is a group, otherwise returns 0. */ static int is_a_group(char *name) { ENTRY item; ENTRY *ret_item; item.key = name; if (hsearch_r(item, FIND, &ret_item, &isgroup_map) == 0) return (0); return (*(int *)ret_item->data); } struct node_host * ifa_exists(char *ifa_name) { struct node_host *n; if (iftab == NULL) ifa_load(); /* check whether this is a group */ if (is_a_group(ifa_name)) { /* fake a node_host */ if ((n = calloc(1, sizeof(*n))) == NULL) err(1, "calloc"); if ((n->ifname = strdup(ifa_name)) == NULL) err(1, "strdup"); return (n); } for (n = iftab; n; n = n->next) { if (n->af == AF_LINK && !strncmp(n->ifname, ifa_name, IFNAMSIZ)) return (n); } return (NULL); } struct node_host * ifa_grouplookup(char *ifa_name, int flags) { struct ifg_req *ifg; struct ifgroupreq ifgr; int s, len; struct node_host *n, *h = NULL; s = get_query_socket(); len = is_a_group(ifa_name); if (len == 0) return (NULL); bzero(&ifgr, sizeof(ifgr)); strlcpy(ifgr.ifgr_name, ifa_name, sizeof(ifgr.ifgr_name)); ifgr.ifgr_len = len; if ((ifgr.ifgr_groups = calloc(1, len)) == NULL) err(1, "calloc"); if (ioctl(s, SIOCGIFGMEMB, (caddr_t)&ifgr) == -1) err(1, "SIOCGIFGMEMB"); for (ifg = ifgr.ifgr_groups; ifg && len >= sizeof(struct ifg_req); ifg++) { len -= sizeof(struct ifg_req); if ((n = ifa_lookup(ifg->ifgrq_member, flags)) == NULL) continue; if (h == NULL) h = n; else { h->tail->next = n; h->tail = n->tail; } } free(ifgr.ifgr_groups); return (h); } struct node_host * ifa_lookup(char *ifa_name, int flags) { struct node_host *p = NULL, *h = NULL, *n = NULL; int got4 = 0, got6 = 0; const char *last_if = NULL; /* first load iftab and isgroup_map */ if (iftab == NULL) ifa_load(); if ((h = ifa_grouplookup(ifa_name, flags)) != NULL) return (h); if (!strncmp(ifa_name, "self", IFNAMSIZ)) ifa_name = NULL; for (p = iftab; p; p = p->next) { if (ifa_skip_if(ifa_name, p)) continue; if ((flags & PFI_AFLAG_BROADCAST) && p->af != AF_INET) continue; if ((flags & PFI_AFLAG_BROADCAST) && !(p->ifa_flags & IFF_BROADCAST)) continue; if ((flags & PFI_AFLAG_PEER) && !(p->ifa_flags & IFF_POINTOPOINT)) continue; if ((flags & PFI_AFLAG_NETWORK) && p->ifindex > 0) continue; if (last_if == NULL || strcmp(last_if, p->ifname)) got4 = got6 = 0; last_if = p->ifname; if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET && got4) continue; if ((flags & PFI_AFLAG_NOALIAS) && p->af == AF_INET6 && got6) continue; if (p->af == AF_INET) got4 = 1; else got6 = 1; n = calloc(1, sizeof(struct node_host)); if (n == NULL) err(1, "address: calloc"); n->af = p->af; if (flags & PFI_AFLAG_BROADCAST) memcpy(&n->addr.v.a.addr, &p->bcast, sizeof(struct pf_addr)); else if (flags & PFI_AFLAG_PEER) memcpy(&n->addr.v.a.addr, &p->peer, sizeof(struct pf_addr)); else memcpy(&n->addr.v.a.addr, &p->addr.v.a.addr, sizeof(struct pf_addr)); if (flags & PFI_AFLAG_NETWORK) set_ipmask(n, unmask(&p->addr.v.a.mask, n->af)); else { if (n->af == AF_INET) { if (p->ifa_flags & IFF_LOOPBACK && p->ifa_flags & IFF_LINK1) memcpy(&n->addr.v.a.mask, &p->addr.v.a.mask, sizeof(struct pf_addr)); else set_ipmask(n, 32); } else set_ipmask(n, 128); } n->ifindex = p->ifindex; n->ifname = strdup(p->ifname); n->next = NULL; n->tail = n; if (h == NULL) h = n; else { h->tail->next = n; h->tail = n; } } return (h); } int ifa_skip_if(const char *filter, struct node_host *p) { int n; if (p->af != AF_INET && p->af != AF_INET6) return (1); if (filter == NULL || !*filter) return (0); if (!strcmp(p->ifname, filter)) return (0); /* exact match */ n = strlen(filter); if (n < 1 || n >= IFNAMSIZ) return (1); /* sanity check */ if (filter[n-1] >= '0' && filter[n-1] <= '9') return (1); /* only do exact match in that case */ if (strncmp(p->ifname, filter, n)) return (1); /* prefix doesn't match */ return (p->ifname[n] < '0' || p->ifname[n] > '9'); } struct node_host * host(const char *s) { struct node_host *h = NULL; int mask, v4mask, v6mask, cont = 1; char *p, *q, *ps; if ((p = strrchr(s, '/')) != NULL) { mask = strtol(p+1, &q, 0); if (!q || *q || mask > 128 || q == (p+1)) { fprintf(stderr, "invalid netmask '%s'\n", p); return (NULL); } if ((ps = malloc(strlen(s) - strlen(p) + 1)) == NULL) err(1, "host: malloc"); strlcpy(ps, s, strlen(s) - strlen(p) + 1); v4mask = v6mask = mask; } else { if ((ps = strdup(s)) == NULL) err(1, "host: strdup"); v4mask = 32; v6mask = 128; mask = -1; } /* IPv4 address? */ if (cont && (h = host_v4(s, mask)) != NULL) cont = 0; /* IPv6 address? */ if (cont && (h = host_v6(ps, v6mask)) != NULL) cont = 0; /* interface with this name exists? */ /* expensive with thousands of interfaces - prioritze IPv4/6 check */ if (cont && (h = host_if(ps, mask)) != NULL) cont = 0; /* dns lookup */ if (cont && (h = host_dns(ps, v4mask, v6mask)) != NULL) cont = 0; free(ps); if (h == NULL || cont == 1) { fprintf(stderr, "no IP address found for %s\n", s); return (NULL); } return (h); } struct node_host * host_if(const char *s, int mask) { struct node_host *n, *h = NULL; char *p, *ps; int flags = 0; if ((ps = strdup(s)) == NULL) err(1, "host_if: strdup"); while ((p = strrchr(ps, ':')) != NULL) { if (!strcmp(p+1, "network")) flags |= PFI_AFLAG_NETWORK; else if (!strcmp(p+1, "broadcast")) flags |= PFI_AFLAG_BROADCAST; else if (!strcmp(p+1, "peer")) flags |= PFI_AFLAG_PEER; else if (!strcmp(p+1, "0")) flags |= PFI_AFLAG_NOALIAS; else { free(ps); return (NULL); } *p = '\0'; } if (flags & (flags - 1) & PFI_AFLAG_MODEMASK) { /* Yep! */ fprintf(stderr, "illegal combination of interface modifiers\n"); free(ps); return (NULL); } if ((flags & (PFI_AFLAG_NETWORK|PFI_AFLAG_BROADCAST)) && mask > -1) { fprintf(stderr, "network or broadcast lookup, but " "extra netmask given\n"); free(ps); return (NULL); } if (ifa_exists(ps) || !strncmp(ps, "self", IFNAMSIZ)) { /* interface with this name exists */ h = ifa_lookup(ps, flags); for (n = h; n != NULL && mask > -1; n = n->next) set_ipmask(n, mask); } free(ps); return (h); } struct node_host * host_v4(const char *s, int mask) { struct node_host *h = NULL; struct in_addr ina; int bits = 32; memset(&ina, 0, sizeof(struct in_addr)); if (strrchr(s, '/') != NULL) { if ((bits = inet_net_pton(AF_INET, s, &ina, sizeof(ina))) == -1) return (NULL); } else { if (inet_pton(AF_INET, s, &ina) != 1) return (NULL); } h = calloc(1, sizeof(struct node_host)); if (h == NULL) err(1, "address: calloc"); h->ifname = NULL; h->af = AF_INET; h->addr.v.a.addr.addr32[0] = ina.s_addr; set_ipmask(h, bits); h->next = NULL; h->tail = h; return (h); } struct node_host * host_v6(const char *s, int mask) { struct addrinfo hints, *res; struct node_host *h = NULL; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET6; hints.ai_socktype = SOCK_DGRAM; /*dummy*/ hints.ai_flags = AI_NUMERICHOST; if (getaddrinfo(s, "0", &hints, &res) == 0) { h = calloc(1, sizeof(struct node_host)); if (h == NULL) err(1, "address: calloc"); h->ifname = NULL; h->af = AF_INET6; memcpy(&h->addr.v.a.addr, &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr, sizeof(h->addr.v.a.addr)); h->ifindex = ((struct sockaddr_in6 *)res->ai_addr)->sin6_scope_id; set_ipmask(h, mask); freeaddrinfo(res); h->next = NULL; h->tail = h; } return (h); } struct node_host * host_dns(const char *s, int v4mask, int v6mask) { struct addrinfo hints, *res0, *res; struct node_host *n, *h = NULL; int error, noalias = 0; int got4 = 0, got6 = 0; char *p, *ps; if ((ps = strdup(s)) == NULL) err(1, "host_dns: strdup"); if ((p = strrchr(ps, ':')) != NULL && !strcmp(p, ":0")) { noalias = 1; *p = '\0'; } memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; /* DUMMY */ error = getaddrinfo(ps, NULL, &hints, &res0); if (error) { free(ps); return (h); } for (res = res0; res; res = res->ai_next) { if (res->ai_family != AF_INET && res->ai_family != AF_INET6) continue; if (noalias) { if (res->ai_family == AF_INET) { if (got4) continue; got4 = 1; } else { if (got6) continue; got6 = 1; } } n = calloc(1, sizeof(struct node_host)); if (n == NULL) err(1, "host_dns: calloc"); n->ifname = NULL; n->af = res->ai_family; if (res->ai_family == AF_INET) { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in *) res->ai_addr)->sin_addr.s_addr, sizeof(struct in_addr)); set_ipmask(n, v4mask); } else { memcpy(&n->addr.v.a.addr, &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr.s6_addr, sizeof(struct in6_addr)); n->ifindex = ((struct sockaddr_in6 *) res->ai_addr)->sin6_scope_id; set_ipmask(n, v6mask); } n->next = NULL; n->tail = n; if (h == NULL) h = n; else { h->tail->next = n; h->tail = n; } } freeaddrinfo(res0); free(ps); return (h); } /* * convert a hostname to a list of addresses and put them in the given buffer. * test: * if set to 1, only simple addresses are accepted (no netblock, no "!"). */ int append_addr(struct pfr_buffer *b, char *s, int test) { char *r; struct node_host *h, *n; int rv, not = 0; for (r = s; *r == '!'; r++) not = !not; if ((n = host(r)) == NULL) { errno = 0; return (-1); } rv = append_addr_host(b, n, test, not); do { h = n; n = n->next; free(h); } while (n != NULL); return (rv); } /* * same as previous function, but with a pre-parsed input and the ability * to "negate" the result. Does not free the node_host list. * not: * setting it to 1 is equivalent to adding "!" in front of parameter s. */ int append_addr_host(struct pfr_buffer *b, struct node_host *n, int test, int not) { int bits; struct pfr_addr addr; do { bzero(&addr, sizeof(addr)); addr.pfra_not = n->not ^ not; addr.pfra_af = n->af; addr.pfra_net = unmask(&n->addr.v.a.mask, n->af); switch (n->af) { case AF_INET: addr.pfra_ip4addr.s_addr = n->addr.v.a.addr.addr32[0]; bits = 32; break; case AF_INET6: memcpy(&addr.pfra_ip6addr, &n->addr.v.a.addr.v6, sizeof(struct in6_addr)); bits = 128; break; default: errno = EINVAL; return (-1); } if ((test && (not || addr.pfra_net != bits)) || addr.pfra_net > bits) { errno = EINVAL; return (-1); } if (pfr_buf_add(b, &addr)) return (-1); } while ((n = n->next) != NULL); return (0); } int pfctl_add_trans(struct pfr_buffer *buf, int rs_num, const char *anchor) { struct pfioc_trans_e trans; bzero(&trans, sizeof(trans)); trans.rs_num = rs_num; if (strlcpy(trans.anchor, anchor, sizeof(trans.anchor)) >= sizeof(trans.anchor)) errx(1, "pfctl_add_trans: strlcpy"); return pfr_buf_add(buf, &trans); } u_int32_t pfctl_get_ticket(struct pfr_buffer *buf, int rs_num, const char *anchor) { struct pfioc_trans_e *p; PFRB_FOREACH(p, buf) if (rs_num == p->rs_num && !strcmp(anchor, p->anchor)) return (p->ticket); errx(1, "pfctl_get_ticket: assertion failed"); } int pfctl_trans(int dev, struct pfr_buffer *buf, u_long cmd, int from) { struct pfioc_trans trans; bzero(&trans, sizeof(trans)); trans.size = buf->pfrb_size - from; trans.esize = sizeof(struct pfioc_trans_e); trans.array = ((struct pfioc_trans_e *)buf->pfrb_caddr) + from; return ioctl(dev, cmd, &trans); } diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h index 2547caa1a8ce..f36e8f1d4ac8 100644 --- a/sbin/pfctl/pfctl_parser.h +++ b/sbin/pfctl/pfctl_parser.h @@ -1,344 +1,346 @@ /* $OpenBSD: pfctl_parser.h,v 1.86 2006/10/31 23:46:25 mcbride Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2001 Daniel Hartmeier * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * $FreeBSD$ */ #ifndef _PFCTL_PARSER_H_ #define _PFCTL_PARSER_H_ +#include + #define PF_OSFP_FILE "/etc/pf.os" #define PF_OPT_DISABLE 0x0001 #define PF_OPT_ENABLE 0x0002 #define PF_OPT_VERBOSE 0x0004 #define PF_OPT_NOACTION 0x0008 #define PF_OPT_QUIET 0x0010 #define PF_OPT_CLRRULECTRS 0x0020 #define PF_OPT_USEDNS 0x0040 #define PF_OPT_VERBOSE2 0x0080 #define PF_OPT_DUMMYACTION 0x0100 #define PF_OPT_DEBUG 0x0200 #define PF_OPT_SHOWALL 0x0400 #define PF_OPT_OPTIMIZE 0x0800 #define PF_OPT_NUMERIC 0x1000 #define PF_OPT_MERGE 0x2000 #define PF_OPT_RECURSE 0x4000 #define PF_TH_ALL 0xFF #define PF_NAT_PROXY_PORT_LOW 50001 #define PF_NAT_PROXY_PORT_HIGH 65535 #define PF_OPTIMIZE_BASIC 0x0001 #define PF_OPTIMIZE_PROFILE 0x0002 #define FCNT_NAMES { \ "searches", \ "inserts", \ "removals", \ NULL \ } struct pfr_buffer; /* forward definition */ struct pfctl { int dev; int opts; int optimize; int loadopt; int asd; /* anchor stack depth */ int bn; /* brace number */ int brace; int tdirty; /* kernel dirty */ #define PFCTL_ANCHOR_STACK_DEPTH 64 - struct pf_anchor *astack[PFCTL_ANCHOR_STACK_DEPTH]; + struct pfctl_anchor *astack[PFCTL_ANCHOR_STACK_DEPTH]; struct pfioc_pooladdr paddr; struct pfioc_altq *paltq; struct pfioc_queue *pqueue; struct pfr_buffer *trans; - struct pf_anchor *anchor, *alast; + struct pfctl_anchor *anchor, *alast; const char *ruleset; /* 'set foo' options */ u_int32_t timeout[PFTM_MAX]; u_int32_t limit[PF_LIMIT_MAX]; u_int32_t debug; u_int32_t hostid; char *ifname; u_int8_t timeout_set[PFTM_MAX]; u_int8_t limit_set[PF_LIMIT_MAX]; u_int8_t debug_set; u_int8_t hostid_set; u_int8_t ifname_set; }; struct node_if { char ifname[IFNAMSIZ]; u_int8_t not; u_int8_t dynamic; /* antispoof */ u_int ifa_flags; struct node_if *next; struct node_if *tail; }; struct node_host { struct pf_addr_wrap addr; struct pf_addr bcast; struct pf_addr peer; sa_family_t af; u_int8_t not; u_int32_t ifindex; /* link-local IPv6 addrs */ char *ifname; u_int ifa_flags; struct node_host *next; struct node_host *tail; }; struct node_os { char *os; pf_osfp_t fingerprint; struct node_os *next; struct node_os *tail; }; struct node_queue_bw { u_int64_t bw_absolute; u_int16_t bw_percent; }; struct node_hfsc_sc { struct node_queue_bw m1; /* slope of 1st segment; bps */ u_int d; /* x-projection of m1; msec */ struct node_queue_bw m2; /* slope of 2nd segment; bps */ u_int8_t used; }; struct node_hfsc_opts { struct node_hfsc_sc realtime; struct node_hfsc_sc linkshare; struct node_hfsc_sc upperlimit; int flags; }; struct node_fairq_sc { struct node_queue_bw m1; /* slope of 1st segment; bps */ u_int d; /* x-projection of m1; msec */ struct node_queue_bw m2; /* slope of 2nd segment; bps */ u_int8_t used; }; struct node_fairq_opts { struct node_fairq_sc linkshare; struct node_queue_bw hogs_bw; u_int nbuckets; int flags; }; struct node_queue_opt { int qtype; union { struct cbq_opts cbq_opts; struct codel_opts codel_opts; struct priq_opts priq_opts; struct node_hfsc_opts hfsc_opts; struct node_fairq_opts fairq_opts; } data; }; #define QPRI_BITSET_SIZE 256 BITSET_DEFINE(qpri_bitset, QPRI_BITSET_SIZE); LIST_HEAD(gen_sc, segment); struct pfctl_altq { struct pf_altq pa; struct { STAILQ_ENTRY(pfctl_altq) link; u_int64_t bwsum; struct qpri_bitset qpris; int children; int root_classes; int default_classes; struct gen_sc lssc; struct gen_sc rtsc; } meta; }; #ifdef __FreeBSD__ /* * XXX * Absolutely this is not correct location to define this. * Should we use an another sperate header file? */ #define SIMPLEQ_HEAD STAILQ_HEAD #define SIMPLEQ_HEAD_INITIALIZER STAILQ_HEAD_INITIALIZER #define SIMPLEQ_ENTRY STAILQ_ENTRY #define SIMPLEQ_FIRST STAILQ_FIRST #define SIMPLEQ_END(head) NULL #define SIMPLEQ_EMPTY STAILQ_EMPTY #define SIMPLEQ_NEXT STAILQ_NEXT /*#define SIMPLEQ_FOREACH STAILQ_FOREACH*/ #define SIMPLEQ_FOREACH(var, head, field) \ for((var) = SIMPLEQ_FIRST(head); \ (var) != SIMPLEQ_END(head); \ (var) = SIMPLEQ_NEXT(var, field)) #define SIMPLEQ_INIT STAILQ_INIT #define SIMPLEQ_INSERT_HEAD STAILQ_INSERT_HEAD #define SIMPLEQ_INSERT_TAIL STAILQ_INSERT_TAIL #define SIMPLEQ_INSERT_AFTER STAILQ_INSERT_AFTER #define SIMPLEQ_REMOVE_HEAD STAILQ_REMOVE_HEAD #endif SIMPLEQ_HEAD(node_tinithead, node_tinit); struct node_tinit { /* table initializer */ SIMPLEQ_ENTRY(node_tinit) entries; struct node_host *host; char *file; }; /* optimizer created tables */ struct pf_opt_tbl { char pt_name[PF_TABLE_NAME_SIZE]; int pt_rulecount; int pt_generated; struct node_tinithead pt_nodes; struct pfr_buffer *pt_buf; }; #define PF_OPT_TABLE_PREFIX "__automatic_" /* optimizer pf_rule container */ struct pf_opt_rule { - struct pf_rule por_rule; + struct pfctl_rule por_rule; struct pf_opt_tbl *por_src_tbl; struct pf_opt_tbl *por_dst_tbl; u_int64_t por_profile_count; TAILQ_ENTRY(pf_opt_rule) por_entry; TAILQ_ENTRY(pf_opt_rule) por_skip_entry[PF_SKIP_COUNT]; }; TAILQ_HEAD(pf_opt_queue, pf_opt_rule); int pfctl_rules(int, char *, int, int, char *, struct pfr_buffer *); -int pfctl_optimize_ruleset(struct pfctl *, struct pf_ruleset *); +int pfctl_optimize_ruleset(struct pfctl *, struct pfctl_ruleset *); -int pfctl_append_rule(struct pfctl *, struct pf_rule *, const char *); +int pfctl_append_rule(struct pfctl *, struct pfctl_rule *, const char *); int pfctl_add_altq(struct pfctl *, struct pf_altq *); int pfctl_add_pool(struct pfctl *, struct pf_pool *, sa_family_t); void pfctl_move_pool(struct pf_pool *, struct pf_pool *); void pfctl_clear_pool(struct pf_pool *); int pfctl_set_timeout(struct pfctl *, const char *, int, int); int pfctl_set_optimization(struct pfctl *, const char *); int pfctl_set_limit(struct pfctl *, const char *, unsigned int); int pfctl_set_logif(struct pfctl *, char *); int pfctl_set_hostid(struct pfctl *, u_int32_t); int pfctl_set_debug(struct pfctl *, char *); int pfctl_set_interface_flags(struct pfctl *, char *, int, int); int parse_config(char *, struct pfctl *); int parse_flags(char *); int pfctl_load_anchors(int, struct pfctl *, struct pfr_buffer *); void print_pool(struct pf_pool *, u_int16_t, u_int16_t, sa_family_t, int); void print_src_node(struct pf_src_node *, int); -void print_rule(struct pf_rule *, const char *, int, int); +void print_rule(struct pfctl_rule *, const char *, int, int); void print_tabledef(const char *, int, int, struct node_tinithead *); void print_status(struct pf_status *, int); void print_running(struct pf_status *); int eval_pfaltq(struct pfctl *, struct pf_altq *, struct node_queue_bw *, struct node_queue_opt *); int eval_pfqueue(struct pfctl *, struct pf_altq *, struct node_queue_bw *, struct node_queue_opt *); void print_altq(const struct pf_altq *, unsigned, struct node_queue_bw *, struct node_queue_opt *); void print_queue(const struct pf_altq *, unsigned, struct node_queue_bw *, int, struct node_queue_opt *); int pfctl_define_table(char *, int, int, const char *, struct pfr_buffer *, u_int32_t); void pfctl_clear_fingerprints(int, int); int pfctl_file_fingerprints(int, int, const char *); pf_osfp_t pfctl_get_fingerprint(const char *); int pfctl_load_fingerprints(int, int); char *pfctl_lookup_fingerprint(pf_osfp_t, char *, size_t); void pfctl_show_fingerprints(int); struct icmptypeent { const char *name; u_int8_t type; }; struct icmpcodeent { const char *name; u_int8_t type; u_int8_t code; }; const struct icmptypeent *geticmptypebynumber(u_int8_t, u_int8_t); const struct icmptypeent *geticmptypebyname(char *, u_int8_t); const struct icmpcodeent *geticmpcodebynumber(u_int8_t, u_int8_t, u_int8_t); const struct icmpcodeent *geticmpcodebyname(u_long, char *, u_int8_t); struct pf_timeout { const char *name; int timeout; }; #define PFCTL_FLAG_FILTER 0x02 #define PFCTL_FLAG_NAT 0x04 #define PFCTL_FLAG_OPTION 0x08 #define PFCTL_FLAG_ALTQ 0x10 #define PFCTL_FLAG_TABLE 0x20 extern const struct pf_timeout pf_timeouts[]; void set_ipmask(struct node_host *, u_int8_t); int check_netmask(struct node_host *, sa_family_t); int unmask(struct pf_addr *, sa_family_t); void ifa_load(void); int get_query_socket(void); struct node_host *ifa_exists(char *); struct node_host *ifa_grouplookup(char *ifa_name, int flags); struct node_host *ifa_lookup(char *, int); struct node_host *host(const char *); int append_addr(struct pfr_buffer *, char *, int); int append_addr_host(struct pfr_buffer *, struct node_host *, int, int); #endif /* _PFCTL_PARSER_H_ */ diff --git a/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c b/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c index dc55c730bc5f..018f3751ca57 100644 --- a/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c +++ b/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c @@ -1,1807 +1,1808 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2005 Philip Paeps * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * $FreeBSD$ */ #define PFIOC_USE_LATEST #include #include #include #include #include #include #include #include #include #include #include #include #include #define SNMPTREE_TYPES #include "pf_oid.h" #include "pf_tree.h" struct lmodule *module; static int dev = -1; static int started; static uint64_t pf_tick; static struct pf_status pfs; enum { IN, OUT }; enum { IPV4, IPV6 }; enum { PASS, BLOCK }; #define PFI_IFTYPE_GROUP 0 #define PFI_IFTYPE_INSTANCE 1 #define PFI_IFTYPE_DETACHED 2 struct pfi_entry { struct pfi_kif pfi; u_int index; TAILQ_ENTRY(pfi_entry) link; }; TAILQ_HEAD(pfi_table, pfi_entry); static struct pfi_table pfi_table; static time_t pfi_table_age; static int pfi_table_count; #define PFI_TABLE_MAXAGE 5 struct pft_entry { struct pfr_tstats pft; u_int index; TAILQ_ENTRY(pft_entry) link; }; TAILQ_HEAD(pft_table, pft_entry); static struct pft_table pft_table; static time_t pft_table_age; static int pft_table_count; #define PFT_TABLE_MAXAGE 5 struct pfa_entry { struct pfr_astats pfas; u_int index; TAILQ_ENTRY(pfa_entry) link; }; TAILQ_HEAD(pfa_table, pfa_entry); static struct pfa_table pfa_table; static time_t pfa_table_age; static int pfa_table_count; #define PFA_TABLE_MAXAGE 5 struct pfq_entry { struct pf_altq altq; u_int index; TAILQ_ENTRY(pfq_entry) link; }; TAILQ_HEAD(pfq_table, pfq_entry); static struct pfq_table pfq_table; static time_t pfq_table_age; static int pfq_table_count; static int altq_enabled = 0; #define PFQ_TABLE_MAXAGE 5 struct pfl_entry { char name[MAXPATHLEN + PF_RULE_LABEL_SIZE]; u_int64_t evals; u_int64_t bytes[2]; u_int64_t pkts[2]; u_int index; TAILQ_ENTRY(pfl_entry) link; }; TAILQ_HEAD(pfl_table, pfl_entry); static struct pfl_table pfl_table; static time_t pfl_table_age; static int pfl_table_count; #define PFL_TABLE_MAXAGE 5 /* Forward declarations */ static int pfi_refresh(void); static int pfq_refresh(void); static int pfs_refresh(void); static int pft_refresh(void); static int pfa_refresh(void); static int pfl_refresh(void); static struct pfi_entry * pfi_table_find(u_int idx); static struct pfq_entry * pfq_table_find(u_int idx); static struct pft_entry * pft_table_find(u_int idx); static struct pfa_entry * pfa_table_find(u_int idx); static struct pfl_entry * pfl_table_find(u_int idx); static int altq_is_enabled(int pfdevice); int pf_status(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; time_t runtime; unsigned char str[128]; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { if (pfs_refresh() == -1) return (SNMP_ERR_GENERR); switch (which) { case LEAF_pfStatusRunning: val->v.uint32 = pfs.running; break; case LEAF_pfStatusRuntime: runtime = (pfs.since > 0) ? time(NULL) - pfs.since : 0; val->v.uint32 = runtime * 100; break; case LEAF_pfStatusDebug: val->v.uint32 = pfs.debug; break; case LEAF_pfStatusHostId: sprintf(str, "0x%08x", ntohl(pfs.hostid)); return (string_get(val, str, strlen(str))); default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } abort(); } int pf_counter(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { if (pfs_refresh() == -1) return (SNMP_ERR_GENERR); switch (which) { case LEAF_pfCounterMatch: val->v.counter64 = pfs.counters[PFRES_MATCH]; break; case LEAF_pfCounterBadOffset: val->v.counter64 = pfs.counters[PFRES_BADOFF]; break; case LEAF_pfCounterFragment: val->v.counter64 = pfs.counters[PFRES_FRAG]; break; case LEAF_pfCounterShort: val->v.counter64 = pfs.counters[PFRES_SHORT]; break; case LEAF_pfCounterNormalize: val->v.counter64 = pfs.counters[PFRES_NORM]; break; case LEAF_pfCounterMemDrop: val->v.counter64 = pfs.counters[PFRES_MEMORY]; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } abort(); } int pf_statetable(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { if (pfs_refresh() == -1) return (SNMP_ERR_GENERR); switch (which) { case LEAF_pfStateTableCount: val->v.uint32 = pfs.states; break; case LEAF_pfStateTableSearches: val->v.counter64 = pfs.fcounters[FCNT_STATE_SEARCH]; break; case LEAF_pfStateTableInserts: val->v.counter64 = pfs.fcounters[FCNT_STATE_INSERT]; break; case LEAF_pfStateTableRemovals: val->v.counter64 = pfs.fcounters[FCNT_STATE_REMOVALS]; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } abort(); } int pf_srcnodes(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { if (pfs_refresh() == -1) return (SNMP_ERR_GENERR); switch (which) { case LEAF_pfSrcNodesCount: val->v.uint32 = pfs.src_nodes; break; case LEAF_pfSrcNodesSearches: val->v.counter64 = pfs.scounters[SCNT_SRC_NODE_SEARCH]; break; case LEAF_pfSrcNodesInserts: val->v.counter64 = pfs.scounters[SCNT_SRC_NODE_INSERT]; break; case LEAF_pfSrcNodesRemovals: val->v.counter64 = pfs.scounters[SCNT_SRC_NODE_REMOVALS]; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } abort(); } int pf_limits(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; struct pfioc_limit pl; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { bzero(&pl, sizeof(struct pfioc_limit)); switch (which) { case LEAF_pfLimitsStates: pl.index = PF_LIMIT_STATES; break; case LEAF_pfLimitsSrcNodes: pl.index = PF_LIMIT_SRC_NODES; break; case LEAF_pfLimitsFrags: pl.index = PF_LIMIT_FRAGS; break; default: return (SNMP_ERR_NOSUCHNAME); } if (ioctl(dev, DIOCGETLIMIT, &pl)) { syslog(LOG_ERR, "pf_limits(): ioctl(): %s", strerror(errno)); return (SNMP_ERR_GENERR); } val->v.uint32 = pl.limit; return (SNMP_ERR_NOERROR); } abort(); } int pf_timeouts(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; struct pfioc_tm pt; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { bzero(&pt, sizeof(struct pfioc_tm)); switch (which) { case LEAF_pfTimeoutsTcpFirst: pt.timeout = PFTM_TCP_FIRST_PACKET; break; case LEAF_pfTimeoutsTcpOpening: pt.timeout = PFTM_TCP_OPENING; break; case LEAF_pfTimeoutsTcpEstablished: pt.timeout = PFTM_TCP_ESTABLISHED; break; case LEAF_pfTimeoutsTcpClosing: pt.timeout = PFTM_TCP_CLOSING; break; case LEAF_pfTimeoutsTcpFinWait: pt.timeout = PFTM_TCP_FIN_WAIT; break; case LEAF_pfTimeoutsTcpClosed: pt.timeout = PFTM_TCP_CLOSED; break; case LEAF_pfTimeoutsUdpFirst: pt.timeout = PFTM_UDP_FIRST_PACKET; break; case LEAF_pfTimeoutsUdpSingle: pt.timeout = PFTM_UDP_SINGLE; break; case LEAF_pfTimeoutsUdpMultiple: pt.timeout = PFTM_UDP_MULTIPLE; break; case LEAF_pfTimeoutsIcmpFirst: pt.timeout = PFTM_ICMP_FIRST_PACKET; break; case LEAF_pfTimeoutsIcmpError: pt.timeout = PFTM_ICMP_ERROR_REPLY; break; case LEAF_pfTimeoutsOtherFirst: pt.timeout = PFTM_OTHER_FIRST_PACKET; break; case LEAF_pfTimeoutsOtherSingle: pt.timeout = PFTM_OTHER_SINGLE; break; case LEAF_pfTimeoutsOtherMultiple: pt.timeout = PFTM_OTHER_MULTIPLE; break; case LEAF_pfTimeoutsFragment: pt.timeout = PFTM_FRAG; break; case LEAF_pfTimeoutsInterval: pt.timeout = PFTM_INTERVAL; break; case LEAF_pfTimeoutsAdaptiveStart: pt.timeout = PFTM_ADAPTIVE_START; break; case LEAF_pfTimeoutsAdaptiveEnd: pt.timeout = PFTM_ADAPTIVE_END; break; case LEAF_pfTimeoutsSrcNode: pt.timeout = PFTM_SRC_NODE; break; default: return (SNMP_ERR_NOSUCHNAME); } if (ioctl(dev, DIOCGETTIMEOUT, &pt)) { syslog(LOG_ERR, "pf_timeouts(): ioctl(): %s", strerror(errno)); return (SNMP_ERR_GENERR); } val->v.integer = pt.seconds; return (SNMP_ERR_NOERROR); } abort(); } int pf_logif(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; unsigned char str[IFNAMSIZ]; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { if (pfs_refresh() == -1) return (SNMP_ERR_GENERR); switch (which) { case LEAF_pfLogInterfaceName: strlcpy(str, pfs.ifname, sizeof str); return (string_get(val, str, strlen(str))); case LEAF_pfLogInterfaceIp4BytesIn: val->v.counter64 = pfs.bcounters[IPV4][IN]; break; case LEAF_pfLogInterfaceIp4BytesOut: val->v.counter64 = pfs.bcounters[IPV4][OUT]; break; case LEAF_pfLogInterfaceIp4PktsInPass: val->v.counter64 = pfs.pcounters[IPV4][IN][PF_PASS]; break; case LEAF_pfLogInterfaceIp4PktsInDrop: val->v.counter64 = pfs.pcounters[IPV4][IN][PF_DROP]; break; case LEAF_pfLogInterfaceIp4PktsOutPass: val->v.counter64 = pfs.pcounters[IPV4][OUT][PF_PASS]; break; case LEAF_pfLogInterfaceIp4PktsOutDrop: val->v.counter64 = pfs.pcounters[IPV4][OUT][PF_DROP]; break; case LEAF_pfLogInterfaceIp6BytesIn: val->v.counter64 = pfs.bcounters[IPV6][IN]; break; case LEAF_pfLogInterfaceIp6BytesOut: val->v.counter64 = pfs.bcounters[IPV6][OUT]; break; case LEAF_pfLogInterfaceIp6PktsInPass: val->v.counter64 = pfs.pcounters[IPV6][IN][PF_PASS]; break; case LEAF_pfLogInterfaceIp6PktsInDrop: val->v.counter64 = pfs.pcounters[IPV6][IN][PF_DROP]; break; case LEAF_pfLogInterfaceIp6PktsOutPass: val->v.counter64 = pfs.pcounters[IPV6][OUT][PF_PASS]; break; case LEAF_pfLogInterfaceIp6PktsOutDrop: val->v.counter64 = pfs.pcounters[IPV6][OUT][PF_DROP]; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } abort(); } int pf_interfaces(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { if ((time(NULL) - pfi_table_age) > PFI_TABLE_MAXAGE) if (pfi_refresh() == -1) return (SNMP_ERR_GENERR); switch (which) { case LEAF_pfInterfacesIfNumber: val->v.uint32 = pfi_table_count; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } abort(); } int pf_iftable(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; struct pfi_entry *e = NULL; if ((time(NULL) - pfi_table_age) > PFI_TABLE_MAXAGE) pfi_refresh(); switch (op) { case SNMP_OP_SET: return (SNMP_ERR_NOT_WRITEABLE); case SNMP_OP_GETNEXT: if ((e = NEXT_OBJECT_INT(&pfi_table, &val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); val->var.len = sub + 1; val->var.subs[sub] = e->index; break; case SNMP_OP_GET: if (val->var.len - sub != 1) return (SNMP_ERR_NOSUCHNAME); if ((e = pfi_table_find(val->var.subs[sub])) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_COMMIT: case SNMP_OP_ROLLBACK: default: abort(); } switch (which) { case LEAF_pfInterfacesIfDescr: return (string_get(val, e->pfi.pfik_name, -1)); case LEAF_pfInterfacesIfType: val->v.integer = PFI_IFTYPE_INSTANCE; break; case LEAF_pfInterfacesIfTZero: val->v.uint32 = (time(NULL) - e->pfi.pfik_tzero) * 100; break; case LEAF_pfInterfacesIfRefsRule: val->v.uint32 = e->pfi.pfik_rulerefs; break; case LEAF_pfInterfacesIf4BytesInPass: val->v.counter64 = e->pfi.pfik_bytes[IPV4][IN][PASS]; break; case LEAF_pfInterfacesIf4BytesInBlock: val->v.counter64 = e->pfi.pfik_bytes[IPV4][IN][BLOCK]; break; case LEAF_pfInterfacesIf4BytesOutPass: val->v.counter64 = e->pfi.pfik_bytes[IPV4][OUT][PASS]; break; case LEAF_pfInterfacesIf4BytesOutBlock: val->v.counter64 = e->pfi.pfik_bytes[IPV4][OUT][BLOCK]; break; case LEAF_pfInterfacesIf4PktsInPass: val->v.counter64 = e->pfi.pfik_packets[IPV4][IN][PASS]; break; case LEAF_pfInterfacesIf4PktsInBlock: val->v.counter64 = e->pfi.pfik_packets[IPV4][IN][BLOCK]; break; case LEAF_pfInterfacesIf4PktsOutPass: val->v.counter64 = e->pfi.pfik_packets[IPV4][OUT][PASS]; break; case LEAF_pfInterfacesIf4PktsOutBlock: val->v.counter64 = e->pfi.pfik_packets[IPV4][OUT][BLOCK]; break; case LEAF_pfInterfacesIf6BytesInPass: val->v.counter64 = e->pfi.pfik_bytes[IPV6][IN][PASS]; break; case LEAF_pfInterfacesIf6BytesInBlock: val->v.counter64 = e->pfi.pfik_bytes[IPV6][IN][BLOCK]; break; case LEAF_pfInterfacesIf6BytesOutPass: val->v.counter64 = e->pfi.pfik_bytes[IPV6][OUT][PASS]; break; case LEAF_pfInterfacesIf6BytesOutBlock: val->v.counter64 = e->pfi.pfik_bytes[IPV6][OUT][BLOCK]; break; case LEAF_pfInterfacesIf6PktsInPass: val->v.counter64 = e->pfi.pfik_packets[IPV6][IN][PASS]; break; case LEAF_pfInterfacesIf6PktsInBlock: val->v.counter64 = e->pfi.pfik_packets[IPV6][IN][BLOCK]; break; case LEAF_pfInterfacesIf6PktsOutPass: val->v.counter64 = e->pfi.pfik_packets[IPV6][OUT][PASS]; break; case LEAF_pfInterfacesIf6PktsOutBlock: val->v.counter64 = e->pfi.pfik_packets[IPV6][OUT][BLOCK]; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } int pf_tables(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { if ((time(NULL) - pft_table_age) > PFT_TABLE_MAXAGE) if (pft_refresh() == -1) return (SNMP_ERR_GENERR); switch (which) { case LEAF_pfTablesTblNumber: val->v.uint32 = pft_table_count; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } abort(); } int pf_tbltable(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; struct pft_entry *e = NULL; if ((time(NULL) - pft_table_age) > PFT_TABLE_MAXAGE) pft_refresh(); switch (op) { case SNMP_OP_SET: return (SNMP_ERR_NOT_WRITEABLE); case SNMP_OP_GETNEXT: if ((e = NEXT_OBJECT_INT(&pft_table, &val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); val->var.len = sub + 1; val->var.subs[sub] = e->index; break; case SNMP_OP_GET: if (val->var.len - sub != 1) return (SNMP_ERR_NOSUCHNAME); if ((e = pft_table_find(val->var.subs[sub])) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_COMMIT: case SNMP_OP_ROLLBACK: default: abort(); } switch (which) { case LEAF_pfTablesTblDescr: return (string_get(val, e->pft.pfrts_name, -1)); case LEAF_pfTablesTblCount: val->v.integer = e->pft.pfrts_cnt; break; case LEAF_pfTablesTblTZero: val->v.uint32 = (time(NULL) - e->pft.pfrts_tzero) * 100; break; case LEAF_pfTablesTblRefsAnchor: val->v.integer = e->pft.pfrts_refcnt[PFR_REFCNT_ANCHOR]; break; case LEAF_pfTablesTblRefsRule: val->v.integer = e->pft.pfrts_refcnt[PFR_REFCNT_RULE]; break; case LEAF_pfTablesTblEvalMatch: val->v.counter64 = e->pft.pfrts_match; break; case LEAF_pfTablesTblEvalNoMatch: val->v.counter64 = e->pft.pfrts_nomatch; break; case LEAF_pfTablesTblBytesInPass: val->v.counter64 = e->pft.pfrts_bytes[PFR_DIR_IN][PFR_OP_PASS]; break; case LEAF_pfTablesTblBytesInBlock: val->v.counter64 = e->pft.pfrts_bytes[PFR_DIR_IN][PFR_OP_BLOCK]; break; case LEAF_pfTablesTblBytesInXPass: val->v.counter64 = e->pft.pfrts_bytes[PFR_DIR_IN][PFR_OP_XPASS]; break; case LEAF_pfTablesTblBytesOutPass: val->v.counter64 = e->pft.pfrts_bytes[PFR_DIR_OUT][PFR_OP_PASS]; break; case LEAF_pfTablesTblBytesOutBlock: val->v.counter64 = e->pft.pfrts_bytes[PFR_DIR_OUT][PFR_OP_BLOCK]; break; case LEAF_pfTablesTblBytesOutXPass: val->v.counter64 = e->pft.pfrts_bytes[PFR_DIR_OUT][PFR_OP_XPASS]; break; case LEAF_pfTablesTblPktsInPass: val->v.counter64 = e->pft.pfrts_packets[PFR_DIR_IN][PFR_OP_PASS]; break; case LEAF_pfTablesTblPktsInBlock: val->v.counter64 = e->pft.pfrts_packets[PFR_DIR_IN][PFR_OP_BLOCK]; break; case LEAF_pfTablesTblPktsInXPass: val->v.counter64 = e->pft.pfrts_packets[PFR_DIR_IN][PFR_OP_XPASS]; break; case LEAF_pfTablesTblPktsOutPass: val->v.counter64 = e->pft.pfrts_packets[PFR_DIR_OUT][PFR_OP_PASS]; break; case LEAF_pfTablesTblPktsOutBlock: val->v.counter64 = e->pft.pfrts_packets[PFR_DIR_OUT][PFR_OP_BLOCK]; break; case LEAF_pfTablesTblPktsOutXPass: val->v.counter64 = e->pft.pfrts_packets[PFR_DIR_OUT][PFR_OP_XPASS]; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } int pf_tbladdr(struct snmp_context __unused *ctx, struct snmp_value __unused *val, u_int __unused sub, u_int __unused vindex, enum snmp_op __unused op) { asn_subid_t which = val->var.subs[sub - 1]; struct pfa_entry *e = NULL; if ((time(NULL) - pfa_table_age) > PFA_TABLE_MAXAGE) pfa_refresh(); switch (op) { case SNMP_OP_SET: return (SNMP_ERR_NOT_WRITEABLE); case SNMP_OP_GETNEXT: if ((e = NEXT_OBJECT_INT(&pfa_table, &val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); val->var.len = sub + 1; val->var.subs[sub] = e->index; break; case SNMP_OP_GET: if (val->var.len - sub != 1) return (SNMP_ERR_NOSUCHNAME); if ((e = pfa_table_find(val->var.subs[sub])) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_COMMIT: case SNMP_OP_ROLLBACK: default: abort(); } switch (which) { case LEAF_pfTablesAddrNetType: if (e->pfas.pfras_a.pfra_af == AF_INET) val->v.integer = pfTablesAddrNetType_ipv4; else if (e->pfas.pfras_a.pfra_af == AF_INET6) val->v.integer = pfTablesAddrNetType_ipv6; else return (SNMP_ERR_GENERR); break; case LEAF_pfTablesAddrNet: if (e->pfas.pfras_a.pfra_af == AF_INET) { return (string_get(val, (u_char *)&e->pfas.pfras_a.pfra_ip4addr, 4)); } else if (e->pfas.pfras_a.pfra_af == AF_INET6) return (string_get(val, (u_char *)&e->pfas.pfras_a.pfra_ip6addr, 16)); else return (SNMP_ERR_GENERR); break; case LEAF_pfTablesAddrPrefix: val->v.integer = (int32_t) e->pfas.pfras_a.pfra_net; break; case LEAF_pfTablesAddrTZero: val->v.uint32 = (time(NULL) - e->pfas.pfras_tzero) * 100; break; case LEAF_pfTablesAddrBytesInPass: val->v.counter64 = e->pfas.pfras_bytes[PFR_DIR_IN][PFR_OP_PASS]; break; case LEAF_pfTablesAddrBytesInBlock: val->v.counter64 = e->pfas.pfras_bytes[PFR_DIR_IN][PFR_OP_BLOCK]; break; case LEAF_pfTablesAddrBytesOutPass: val->v.counter64 = e->pfas.pfras_bytes[PFR_DIR_OUT][PFR_OP_PASS]; break; case LEAF_pfTablesAddrBytesOutBlock: val->v.counter64 = e->pfas.pfras_bytes[PFR_DIR_OUT][PFR_OP_BLOCK]; break; case LEAF_pfTablesAddrPktsInPass: val->v.counter64 = e->pfas.pfras_packets[PFR_DIR_IN][PFR_OP_PASS]; break; case LEAF_pfTablesAddrPktsInBlock: val->v.counter64 = e->pfas.pfras_packets[PFR_DIR_IN][PFR_OP_BLOCK]; break; case LEAF_pfTablesAddrPktsOutPass: val->v.counter64 = e->pfas.pfras_packets[PFR_DIR_OUT][PFR_OP_PASS]; break; case LEAF_pfTablesAddrPktsOutBlock: val->v.counter64 = e->pfas.pfras_packets[PFR_DIR_OUT][PFR_OP_BLOCK]; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } int pf_altq_num(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; if (!altq_enabled) return (SNMP_ERR_NOSUCHNAME); if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { if ((time(NULL) - pfq_table_age) > PFQ_TABLE_MAXAGE) if (pfq_refresh() == -1) return (SNMP_ERR_GENERR); switch (which) { case LEAF_pfAltqQueueNumber: val->v.uint32 = pfq_table_count; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } abort(); return (SNMP_ERR_GENERR); } int pf_altqq(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; struct pfq_entry *e = NULL; if (!altq_enabled) return (SNMP_ERR_NOSUCHNAME); if ((time(NULL) - pfq_table_age) > PFQ_TABLE_MAXAGE) pfq_refresh(); switch (op) { case SNMP_OP_SET: return (SNMP_ERR_NOT_WRITEABLE); case SNMP_OP_GETNEXT: if ((e = NEXT_OBJECT_INT(&pfq_table, &val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); val->var.len = sub + 1; val->var.subs[sub] = e->index; break; case SNMP_OP_GET: if (val->var.len - sub != 1) return (SNMP_ERR_NOSUCHNAME); if ((e = pfq_table_find(val->var.subs[sub])) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_COMMIT: case SNMP_OP_ROLLBACK: default: abort(); } switch (which) { case LEAF_pfAltqQueueDescr: return (string_get(val, e->altq.qname, -1)); case LEAF_pfAltqQueueParent: return (string_get(val, e->altq.parent, -1)); case LEAF_pfAltqQueueScheduler: val->v.integer = e->altq.scheduler; break; case LEAF_pfAltqQueueBandwidth: val->v.uint32 = (e->altq.bandwidth > UINT_MAX) ? UINT_MAX : (u_int32_t)e->altq.bandwidth; break; case LEAF_pfAltqQueuePriority: val->v.integer = e->altq.priority; break; case LEAF_pfAltqQueueLimit: val->v.integer = e->altq.qlimit; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } int pf_labels(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { if ((time(NULL) - pfl_table_age) > PFL_TABLE_MAXAGE) if (pfl_refresh() == -1) return (SNMP_ERR_GENERR); switch (which) { case LEAF_pfLabelsLblNumber: val->v.uint32 = pfl_table_count; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } abort(); return (SNMP_ERR_GENERR); } int pf_lbltable(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; struct pfl_entry *e = NULL; if ((time(NULL) - pfl_table_age) > PFL_TABLE_MAXAGE) pfl_refresh(); switch (op) { case SNMP_OP_SET: return (SNMP_ERR_NOT_WRITEABLE); case SNMP_OP_GETNEXT: if ((e = NEXT_OBJECT_INT(&pfl_table, &val->var, sub)) == NULL) return (SNMP_ERR_NOSUCHNAME); val->var.len = sub + 1; val->var.subs[sub] = e->index; break; case SNMP_OP_GET: if (val->var.len - sub != 1) return (SNMP_ERR_NOSUCHNAME); if ((e = pfl_table_find(val->var.subs[sub])) == NULL) return (SNMP_ERR_NOSUCHNAME); break; case SNMP_OP_COMMIT: case SNMP_OP_ROLLBACK: default: abort(); } switch (which) { case LEAF_pfLabelsLblName: return (string_get(val, e->name, -1)); case LEAF_pfLabelsLblEvals: val->v.counter64 = e->evals; break; case LEAF_pfLabelsLblBytesIn: val->v.counter64 = e->bytes[IN]; break; case LEAF_pfLabelsLblBytesOut: val->v.counter64 = e->bytes[OUT]; break; case LEAF_pfLabelsLblPktsIn: val->v.counter64 = e->pkts[IN]; break; case LEAF_pfLabelsLblPktsOut: val->v.counter64 = e->pkts[OUT]; break; default: return (SNMP_ERR_NOSUCHNAME); } return (SNMP_ERR_NOERROR); } static struct pfi_entry * pfi_table_find(u_int idx) { struct pfi_entry *e; TAILQ_FOREACH(e, &pfi_table, link) if (e->index == idx) return (e); return (NULL); } static struct pfq_entry * pfq_table_find(u_int idx) { struct pfq_entry *e; TAILQ_FOREACH(e, &pfq_table, link) if (e->index == idx) return (e); return (NULL); } static struct pft_entry * pft_table_find(u_int idx) { struct pft_entry *e; TAILQ_FOREACH(e, &pft_table, link) if (e->index == idx) return (e); return (NULL); } static struct pfa_entry * pfa_table_find(u_int idx) { struct pfa_entry *e; TAILQ_FOREACH(e, &pfa_table, link) if (e->index == idx) return (e); return (NULL); } static struct pfl_entry * pfl_table_find(u_int idx) { struct pfl_entry *e; TAILQ_FOREACH(e, &pfl_table, link) if (e->index == idx) return (e); return (NULL); } static int pfi_refresh(void) { struct pfioc_iface io; struct pfi_kif *p = NULL; struct pfi_entry *e; int i, numifs = 1; if (started && this_tick <= pf_tick) return (0); while (!TAILQ_EMPTY(&pfi_table)) { e = TAILQ_FIRST(&pfi_table); TAILQ_REMOVE(&pfi_table, e, link); free(e); } bzero(&io, sizeof(io)); io.pfiio_esize = sizeof(struct pfi_kif); for (;;) { p = reallocf(p, numifs * sizeof(struct pfi_kif)); if (p == NULL) { syslog(LOG_ERR, "pfi_refresh(): reallocf() numifs=%d: %s", numifs, strerror(errno)); goto err2; } io.pfiio_size = numifs; io.pfiio_buffer = p; if (ioctl(dev, DIOCIGETIFACES, &io)) { syslog(LOG_ERR, "pfi_refresh(): ioctl(): %s", strerror(errno)); goto err2; } if (numifs >= io.pfiio_size) break; numifs = io.pfiio_size; } for (i = 0; i < numifs; i++) { e = malloc(sizeof(struct pfi_entry)); if (e == NULL) goto err1; e->index = i + 1; memcpy(&e->pfi, p+i, sizeof(struct pfi_kif)); TAILQ_INSERT_TAIL(&pfi_table, e, link); } pfi_table_age = time(NULL); pfi_table_count = numifs; pf_tick = this_tick; free(p); return (0); err1: while (!TAILQ_EMPTY(&pfi_table)) { e = TAILQ_FIRST(&pfi_table); TAILQ_REMOVE(&pfi_table, e, link); free(e); } err2: free(p); return(-1); } static int pfq_refresh(void) { struct pfioc_altq pa; struct pfq_entry *e; int i, numqs, ticket; if (started && this_tick <= pf_tick) return (0); while (!TAILQ_EMPTY(&pfq_table)) { e = TAILQ_FIRST(&pfq_table); TAILQ_REMOVE(&pfq_table, e, link); free(e); } bzero(&pa, sizeof(pa)); pa.version = PFIOC_ALTQ_VERSION; if (ioctl(dev, DIOCGETALTQS, &pa)) { syslog(LOG_ERR, "pfq_refresh: ioctl(DIOCGETALTQS): %s", strerror(errno)); return (-1); } numqs = pa.nr; ticket = pa.ticket; for (i = 0; i < numqs; i++) { e = malloc(sizeof(struct pfq_entry)); if (e == NULL) { syslog(LOG_ERR, "pfq_refresh(): " "malloc(): %s", strerror(errno)); goto err; } pa.ticket = ticket; pa.nr = i; if (ioctl(dev, DIOCGETALTQ, &pa)) { syslog(LOG_ERR, "pfq_refresh(): " "ioctl(DIOCGETALTQ): %s", strerror(errno)); goto err; } if (pa.altq.qid > 0) { memcpy(&e->altq, &pa.altq, sizeof(struct pf_altq)); e->index = pa.altq.qid; pfq_table_count = i; INSERT_OBJECT_INT_LINK_INDEX(e, &pfq_table, link, index); } } pfq_table_age = time(NULL); pf_tick = this_tick; return (0); err: free(e); while (!TAILQ_EMPTY(&pfq_table)) { e = TAILQ_FIRST(&pfq_table); TAILQ_REMOVE(&pfq_table, e, link); free(e); } return(-1); } static int pfs_refresh(void) { if (started && this_tick <= pf_tick) return (0); bzero(&pfs, sizeof(struct pf_status)); if (ioctl(dev, DIOCGETSTATUS, &pfs)) { syslog(LOG_ERR, "pfs_refresh(): ioctl(): %s", strerror(errno)); return (-1); } pf_tick = this_tick; return (0); } static int pft_refresh(void) { struct pfioc_table io; struct pfr_tstats *t = NULL; struct pft_entry *e; int i, numtbls = 1; if (started && this_tick <= pf_tick) return (0); while (!TAILQ_EMPTY(&pft_table)) { e = TAILQ_FIRST(&pft_table); TAILQ_REMOVE(&pft_table, e, link); free(e); } bzero(&io, sizeof(io)); io.pfrio_esize = sizeof(struct pfr_tstats); for (;;) { t = reallocf(t, numtbls * sizeof(struct pfr_tstats)); if (t == NULL) { syslog(LOG_ERR, "pft_refresh(): reallocf() numtbls=%d: %s", numtbls, strerror(errno)); goto err2; } io.pfrio_size = numtbls; io.pfrio_buffer = t; if (ioctl(dev, DIOCRGETTSTATS, &io)) { syslog(LOG_ERR, "pft_refresh(): ioctl(): %s", strerror(errno)); goto err2; } if (numtbls >= io.pfrio_size) break; numtbls = io.pfrio_size; } for (i = 0; i < numtbls; i++) { e = malloc(sizeof(struct pft_entry)); if (e == NULL) goto err1; e->index = i + 1; memcpy(&e->pft, t+i, sizeof(struct pfr_tstats)); TAILQ_INSERT_TAIL(&pft_table, e, link); } pft_table_age = time(NULL); pft_table_count = numtbls; pf_tick = this_tick; free(t); return (0); err1: while (!TAILQ_EMPTY(&pft_table)) { e = TAILQ_FIRST(&pft_table); TAILQ_REMOVE(&pft_table, e, link); free(e); } err2: free(t); return(-1); } static int pfa_table_addrs(u_int sidx, struct pfr_table *pt) { struct pfioc_table io; struct pfr_astats *t = NULL; struct pfa_entry *e; int i, numaddrs = 1; if (pt == NULL) return (-1); memset(&io, 0, sizeof(io)); strlcpy(io.pfrio_table.pfrt_name, pt->pfrt_name, sizeof(io.pfrio_table.pfrt_name)); for (;;) { t = reallocf(t, numaddrs * sizeof(struct pfr_astats)); if (t == NULL) { syslog(LOG_ERR, "pfa_table_addrs(): reallocf(): %s", strerror(errno)); numaddrs = -1; goto error; } memset(t, 0, sizeof(*t)); io.pfrio_size = numaddrs; io.pfrio_buffer = t; io.pfrio_esize = sizeof(struct pfr_astats); if (ioctl(dev, DIOCRGETASTATS, &io)) { syslog(LOG_ERR, "pfa_table_addrs(): ioctl() on %s: %s", pt->pfrt_name, strerror(errno)); numaddrs = -1; break; } if (numaddrs >= io.pfrio_size) break; numaddrs = io.pfrio_size; } for (i = 0; i < numaddrs; i++) { if ((t + i)->pfras_a.pfra_af != AF_INET && (t + i)->pfras_a.pfra_af != AF_INET6) { numaddrs = i; break; } e = (struct pfa_entry *)malloc(sizeof(struct pfa_entry)); if (e == NULL) { syslog(LOG_ERR, "pfa_table_addrs(): malloc(): %s", strerror(errno)); numaddrs = -1; break; } e->index = sidx + i; memcpy(&e->pfas, t + i, sizeof(struct pfr_astats)); TAILQ_INSERT_TAIL(&pfa_table, e, link); } free(t); error: return (numaddrs); } static int pfa_refresh(void) { struct pfioc_table io; struct pfr_table *pt = NULL, *it = NULL; struct pfa_entry *e; int i, numtbls = 1, cidx, naddrs; if (started && this_tick <= pf_tick) return (0); while (!TAILQ_EMPTY(&pfa_table)) { e = TAILQ_FIRST(&pfa_table); TAILQ_REMOVE(&pfa_table, e, link); free(e); } memset(&io, 0, sizeof(io)); io.pfrio_esize = sizeof(struct pfr_table); for (;;) { pt = reallocf(pt, numtbls * sizeof(struct pfr_table)); if (pt == NULL) { syslog(LOG_ERR, "pfa_refresh(): reallocf() %s", strerror(errno)); return (-1); } memset(pt, 0, sizeof(*pt)); io.pfrio_size = numtbls; io.pfrio_buffer = pt; if (ioctl(dev, DIOCRGETTABLES, &io)) { syslog(LOG_ERR, "pfa_refresh(): ioctl(): %s", strerror(errno)); goto err2; } if (numtbls >= io.pfrio_size) break; numtbls = io.pfrio_size; } cidx = 1; for (it = pt, i = 0; i < numtbls; it++, i++) { /* * Skip the table if not active - ioctl(DIOCRGETASTATS) will * return ESRCH for this entry anyway. */ if (!(it->pfrt_flags & PFR_TFLAG_ACTIVE)) continue; if ((naddrs = pfa_table_addrs(cidx, it)) < 0) goto err1; cidx += naddrs; } pfa_table_age = time(NULL); pfa_table_count = cidx; pf_tick = this_tick; free(pt); return (0); err1: while (!TAILQ_EMPTY(&pfa_table)) { e = TAILQ_FIRST(&pfa_table); TAILQ_REMOVE(&pfa_table, e, link); free(e); } err2: free(pt); return (-1); } static int pfl_scan_ruleset(const char *path) { struct pfioc_rule pr; + struct pfctl_rule rule; struct pfl_entry *e; u_int32_t nr, i; bzero(&pr, sizeof(pr)); strlcpy(pr.anchor, path, sizeof(pr.anchor)); pr.rule.action = PF_PASS; if (ioctl(dev, DIOCGETRULES, &pr)) { syslog(LOG_ERR, "pfl_scan_ruleset: ioctl(DIOCGETRULES): %s", strerror(errno)); goto err; } for (nr = pr.nr, i = 0; i < nr; i++) { pr.nr = i; - if (pfctl_add_rule(dev, &pr.rule, pr.anchor, pr.anchor_call, + if (pfctl_add_rule(dev, &rule, pr.anchor, pr.anchor_call, pr.ticket, pr.pool_ticket)) { syslog(LOG_ERR, "pfl_scan_ruleset: ioctl(DIOCGETRULE):" " %s", strerror(errno)); goto err; } - if (pr.rule.label[0]) { + if (rule.label[0]) { e = (struct pfl_entry *)malloc(sizeof(*e)); if (e == NULL) goto err; strlcpy(e->name, path, sizeof(e->name)); if (path[0]) strlcat(e->name, "/", sizeof(e->name)); - strlcat(e->name, pr.rule.label, sizeof(e->name)); + strlcat(e->name, rule.label, sizeof(e->name)); - e->evals = pr.rule.evaluations; - e->bytes[IN] = pr.rule.bytes[IN]; - e->bytes[OUT] = pr.rule.bytes[OUT]; - e->pkts[IN] = pr.rule.packets[IN]; - e->pkts[OUT] = pr.rule.packets[OUT]; + e->evals = rule.evaluations; + e->bytes[IN] = rule.bytes[IN]; + e->bytes[OUT] = rule.bytes[OUT]; + e->pkts[IN] = rule.packets[IN]; + e->pkts[OUT] = rule.packets[OUT]; e->index = ++pfl_table_count; TAILQ_INSERT_TAIL(&pfl_table, e, link); } } return (0); err: return (-1); } static int pfl_walk_rulesets(const char *path) { struct pfioc_ruleset prs; char newpath[MAXPATHLEN]; u_int32_t nr, i; if (pfl_scan_ruleset(path)) goto err; bzero(&prs, sizeof(prs)); strlcpy(prs.path, path, sizeof(prs.path)); if (ioctl(dev, DIOCGETRULESETS, &prs)) { syslog(LOG_ERR, "pfl_walk_rulesets: ioctl(DIOCGETRULESETS): %s", strerror(errno)); goto err; } for (nr = prs.nr, i = 0; i < nr; i++) { prs.nr = i; if (ioctl(dev, DIOCGETRULESET, &prs)) { syslog(LOG_ERR, "pfl_walk_rulesets: ioctl(DIOCGETRULESET):" " %s", strerror(errno)); goto err; } if (strcmp(prs.name, PF_RESERVED_ANCHOR) == 0) continue; strlcpy(newpath, path, sizeof(newpath)); if (path[0]) strlcat(newpath, "/", sizeof(newpath)); strlcat(newpath, prs.name, sizeof(newpath)); if (pfl_walk_rulesets(newpath)) goto err; } return (0); err: return (-1); } static int pfl_refresh(void) { struct pfl_entry *e; if (started && this_tick <= pf_tick) return (0); while (!TAILQ_EMPTY(&pfl_table)) { e = TAILQ_FIRST(&pfl_table); TAILQ_REMOVE(&pfl_table, e, link); free(e); } pfl_table_count = 0; if (pfl_walk_rulesets("")) goto err; pfl_table_age = time(NULL); pf_tick = this_tick; return (0); err: while (!TAILQ_EMPTY(&pfl_table)) { e = TAILQ_FIRST(&pfl_table); TAILQ_REMOVE(&pfl_table, e, link); free(e); } pfl_table_count = 0; return (-1); } /* * check whether altq support is enabled in kernel */ static int altq_is_enabled(int pfdev) { struct pfioc_altq pa; errno = 0; pa.version = PFIOC_ALTQ_VERSION; if (ioctl(pfdev, DIOCGETALTQS, &pa)) { if (errno == ENODEV) { syslog(LOG_INFO, "No ALTQ support in kernel\n" "ALTQ related functions disabled\n"); return (0); } else { syslog(LOG_ERR, "DIOCGETALTQS returned an error: %s", strerror(errno)); return (-1); } } return (1); } /* * Implement the bsnmpd module interface */ static int pf_init(struct lmodule *mod, int __unused argc, char __unused *argv[]) { module = mod; if ((dev = open("/dev/pf", O_RDONLY)) == -1) { syslog(LOG_ERR, "pf_init(): open(): %s\n", strerror(errno)); return (-1); } if ((altq_enabled = altq_is_enabled(dev)) == -1) { syslog(LOG_ERR, "pf_init(): altq test failed"); return (-1); } /* Prepare internal state */ TAILQ_INIT(&pfi_table); TAILQ_INIT(&pfq_table); TAILQ_INIT(&pft_table); TAILQ_INIT(&pfa_table); TAILQ_INIT(&pfl_table); pfi_refresh(); if (altq_enabled) { pfq_refresh(); } pfs_refresh(); pft_refresh(); pfa_refresh(); pfl_refresh(); started = 1; return (0); } static int pf_fini(void) { struct pfi_entry *i1, *i2; struct pfq_entry *q1, *q2; struct pft_entry *t1, *t2; struct pfa_entry *a1, *a2; struct pfl_entry *l1, *l2; /* Empty the list of interfaces */ i1 = TAILQ_FIRST(&pfi_table); while (i1 != NULL) { i2 = TAILQ_NEXT(i1, link); free(i1); i1 = i2; } /* List of queues */ q1 = TAILQ_FIRST(&pfq_table); while (q1 != NULL) { q2 = TAILQ_NEXT(q1, link); free(q1); q1 = q2; } /* List of tables */ t1 = TAILQ_FIRST(&pft_table); while (t1 != NULL) { t2 = TAILQ_NEXT(t1, link); free(t1); t1 = t2; } /* List of table addresses */ a1 = TAILQ_FIRST(&pfa_table); while (a1 != NULL) { a2 = TAILQ_NEXT(a1, link); free(a1); a1 = a2; } /* And the list of labeled filter rules */ l1 = TAILQ_FIRST(&pfl_table); while (l1 != NULL) { l2 = TAILQ_NEXT(l1, link); free(l1); l1 = l2; } close(dev); return (0); } static void pf_dump(void) { pfi_refresh(); if (altq_enabled) { pfq_refresh(); } pft_refresh(); pfa_refresh(); pfl_refresh(); syslog(LOG_ERR, "Dump: pfi_table_age = %jd", (intmax_t)pfi_table_age); syslog(LOG_ERR, "Dump: pfi_table_count = %d", pfi_table_count); syslog(LOG_ERR, "Dump: pfq_table_age = %jd", (intmax_t)pfq_table_age); syslog(LOG_ERR, "Dump: pfq_table_count = %d", pfq_table_count); syslog(LOG_ERR, "Dump: pft_table_age = %jd", (intmax_t)pft_table_age); syslog(LOG_ERR, "Dump: pft_table_count = %d", pft_table_count); syslog(LOG_ERR, "Dump: pfa_table_age = %jd", (intmax_t)pfa_table_age); syslog(LOG_ERR, "Dump: pfa_table_count = %d", pfa_table_count); syslog(LOG_ERR, "Dump: pfl_table_age = %jd", (intmax_t)pfl_table_age); syslog(LOG_ERR, "Dump: pfl_table_count = %d", pfl_table_count); } const struct snmp_module config = { .comment = "This module implements a MIB for the pf packet filter.", .init = pf_init, .fini = pf_fini, .tree = pf_ctree, .dump = pf_dump, .tree_size = pf_CTREE_SIZE, };