diff options
Diffstat (limited to 'sbin/pfctl')
-rw-r--r-- | sbin/pfctl/parse.y | 276 | ||||
-rw-r--r-- | sbin/pfctl/pfctl.8 | 102 | ||||
-rw-r--r-- | sbin/pfctl/pfctl.c | 163 | ||||
-rw-r--r-- | sbin/pfctl/pfctl.h | 43 | ||||
-rw-r--r-- | sbin/pfctl/pfctl_optimize.c | 3 | ||||
-rw-r--r-- | sbin/pfctl/pfctl_parser.c | 68 | ||||
-rw-r--r-- | sbin/pfctl/pfctl_parser.h | 14 | ||||
-rw-r--r-- | sbin/pfctl/pfctl_radix.c | 15 | ||||
-rw-r--r-- | sbin/pfctl/pfctl_table.c | 86 | ||||
-rw-r--r-- | sbin/pfctl/tests/files/pf1075.in | 1 | ||||
-rw-r--r-- | sbin/pfctl/tests/files/pf1075.ok | 1 | ||||
-rw-r--r-- | sbin/pfctl/tests/pfctl_test_list.inc | 1 |
12 files changed, 579 insertions, 194 deletions
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y index 46e9f60fe48e..0f7702fc4630 100644 --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -267,7 +267,7 @@ static struct filter_opts { #define FOM_SETTOS 0x0100 #define FOM_SCRUB_TCP 0x0200 #define FOM_SETPRIO 0x0400 -#define FOM_ONCE 0x1000 /* not yet implemmented */ +#define FOM_ONCE 0x1000 #define FOM_PRIO 0x2000 #define FOM_SETDELAY 0x4000 #define FOM_FRAGCACHE 0x8000 /* does not exist in OpenBSD */ @@ -372,8 +372,8 @@ int validate_range(uint8_t, uint16_t, uint16_t); 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 pfctl_rule *, int); -int filter_consistent(struct pfctl_rule *, int); +int rule_consistent(struct pfctl_rule *); +int filter_consistent(struct pfctl_rule *); int nat_consistent(struct pfctl_rule *); int rdr_consistent(struct pfctl_rule *); int process_tabledef(char *, struct table_opts *, int); @@ -403,7 +403,7 @@ void expand_rule(struct pfctl_rule *, bool, struct node_if *, 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_if *, - struct node_icmp *, const char *); + struct node_icmp *); int expand_altq(struct pf_altq *, struct node_if *, struct node_queue *, struct node_queue_bw bwspec, struct node_queue_opt *); @@ -420,6 +420,8 @@ int rt_tableid_max(void); void mv_rules(struct pfctl_ruleset *, struct pfctl_ruleset *); void mv_eth_rules(struct pfctl_eth_ruleset *, struct pfctl_eth_ruleset *); +void mv_tables(struct pfctl *, struct pfr_ktablehead *, + struct pfctl_anchor *, struct pfctl_anchor *); 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); @@ -430,6 +432,7 @@ int filteropts_to_rule(struct pfctl_rule *, struct filter_opts *); struct node_mac* node_mac_from_string(const char *); struct node_mac* node_mac_from_string_masklen(const char *, int); struct node_mac* node_mac_from_string_mask(const char *, const char *); +static bool pfctl_setup_anchor(struct pfctl_rule *, struct pfctl *, char *); static TAILQ_HEAD(loadanchorshead, loadanchors) loadanchorshead = TAILQ_HEAD_INITIALIZER(loadanchorshead); @@ -539,7 +542,7 @@ int parseport(char *, struct range *r, int); %token ALTQ CBQ CODEL PRIQ HFSC FAIRQ BANDWIDTH TBRSIZE LINKSHARE REALTIME %token UPPERLIMIT QUEUE PRIORITY QLIMIT HOGS BUCKETS RTABLE TARGET INTERVAL %token DNPIPE DNQUEUE RIDENTIFIER -%token LOAD RULESET_OPTIMIZATION PRIO +%token LOAD RULESET_OPTIMIZATION PRIO ONCE %token STICKYADDRESS ENDPI MAXSRCSTATES MAXSRCNODES SOURCETRACK GLOBAL RULE %token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW ALLOW_RELATED %token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS @@ -949,6 +952,7 @@ anchorname : STRING { pfa_anchorlist : /* empty */ | pfa_anchorlist '\n' + | pfa_anchorlist tabledef '\n' | pfa_anchorlist pfrule '\n' | pfa_anchorlist anchorrule '\n' | pfa_anchorlist include '\n' @@ -974,7 +978,7 @@ pfa_anchor : '{' 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"); + err(1, "pfa_anchor: pf_find_or_create_ruleset (%s)", ta); pf->astack[pf->asd] = rs->anchor; pf->anchor = rs->anchor; } '\n' pfa_anchorlist '}' @@ -999,43 +1003,9 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto } pfctl_init_rule(&r); + if (! pfctl_setup_anchor(&r, pf, $2)) + YYERROR; - if (pf->astack[pf->asd + 1]) { - if ($2 && strchr($2, '/') != NULL) { - free($2); - yyerror("anchor paths containing '/' " - "cannot be used for inline anchors."); - YYERROR; - } - - /* Move inline rules into relative location. */ - 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; @@ -1071,8 +1041,7 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto expand_rule(&r, false, $5, NULL, NULL, NULL, $7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host, - $8.dst.port, $9.uid, $9.gid, $9.rcv, $9.icmpspec, - pf->astack[pf->asd + 1] ? pf->alast->name : $2); + $8.dst.port, $9.uid, $9.gid, $9.rcv, $9.icmpspec); free($2); pf->astack[pf->asd + 1] = NULL; } @@ -1085,6 +1054,8 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto } pfctl_init_rule(&r); + if (! pfctl_setup_anchor(&r, pf, $2)) + YYERROR; r.action = PF_NAT; r.af = $4; @@ -1095,7 +1066,7 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto expand_rule(&r, false, $3, NULL, NULL, NULL, $5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host, - $6.dst.port, 0, 0, 0, 0, $2); + $6.dst.port, 0, 0, 0, 0); free($2); } | RDRANCHOR string interface af proto fromto rtable { @@ -1107,6 +1078,8 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto } pfctl_init_rule(&r); + if (! pfctl_setup_anchor(&r, pf, $2)) + YYERROR; r.action = PF_RDR; r.af = $4; @@ -1138,7 +1111,7 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto expand_rule(&r, false, $3, NULL, NULL, NULL, $5, $6.src_os, $6.src.host, $6.src.port, $6.dst.host, - $6.dst.port, 0, 0, 0, 0, $2); + $6.dst.port, 0, 0, 0, 0); free($2); } | BINATANCHOR string interface af proto fromto rtable { @@ -1150,6 +1123,8 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto } pfctl_init_rule(&r); + if (! pfctl_setup_anchor(&r, pf, $2)) + YYERROR; r.action = PF_BINAT; r.af = $4; @@ -1174,7 +1149,7 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto decide_address_family($6.src.host, &r.af); decide_address_family($6.dst.host, &r.af); - pfctl_append_rule(pf, &r, $2); + pfctl_append_rule(pf, &r); free($2); } ; @@ -1461,7 +1436,7 @@ scrubrule : scrubaction dir logquick interface af proto fromto scrub_opts expand_rule(&r, false, $4, NULL, NULL, NULL, $6, $7.src_os, $7.src.host, $7.src.port, $7.dst.host, - $7.dst.port, NULL, NULL, NULL, NULL, ""); + $7.dst.port, NULL, NULL, NULL, NULL); } ; @@ -1626,7 +1601,7 @@ antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts { if (h != NULL) expand_rule(&r, false, j, NULL, NULL, NULL, NULL, NULL, h, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, ""); + NULL, NULL, NULL, NULL, NULL); if ((i->ifa_flags & IFF_LOOPBACK) == 0) { bzero(&r, sizeof(r)); @@ -1649,7 +1624,7 @@ antispoof : ANTISPOOF logquick antispoof_ifspc af antispoof_opts { expand_rule(&r, false, NULL, NULL, NULL, NULL, NULL, NULL, h, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, ""); + NULL, NULL, NULL, NULL); } else free(hh); } @@ -2732,7 +2707,7 @@ pfrule : action dir logquick interface route af proto fromto expand_rule(&r, false, $4, $9.nat, $9.rdr, $5.redirspec, $7, $8.src_os, $8.src.host, $8.src.port, $8.dst.host, - $8.dst.port, $9.uid, $9.gid, $9.rcv, $9.icmpspec, ""); + $8.dst.port, $9.uid, $9.gid, $9.rcv, $9.icmpspec); } ; @@ -3032,6 +3007,9 @@ filter_opt : USER uids { } filter_opts.max_pkt_size = $2; } + | ONCE { + filter_opts.marker |= FOM_ONCE; + } | filter_sets ; @@ -4864,7 +4842,7 @@ natrule : nataction interface af proto fromto tag tagged rtable expand_rule(&r, false, $2, NULL, $9, NULL, $4, $5.src_os, $5.src.host, $5.src.port, $5.dst.host, - $5.dst.port, 0, 0, 0, 0, ""); + $5.dst.port, 0, 0, 0, 0); } ; @@ -5043,7 +5021,7 @@ binatrule : no BINAT natpasslog interface af proto FROM ipspec toipspec tag free($13); } - pfctl_append_rule(pf, &binat, ""); + pfctl_append_rule(pf, &binat); } ; @@ -5270,7 +5248,7 @@ disallow_alias(struct node_host *h, const char *fmt) } int -rule_consistent(struct pfctl_rule *r, int anchor_call) +rule_consistent(struct pfctl_rule *r) { int problems = 0; @@ -5280,7 +5258,7 @@ rule_consistent(struct pfctl_rule *r, int anchor_call) case PF_DROP: case PF_SCRUB: case PF_NOSCRUB: - problems = filter_consistent(r, anchor_call); + problems = filter_consistent(r); break; case PF_NAT: case PF_NONAT: @@ -5299,7 +5277,7 @@ rule_consistent(struct pfctl_rule *r, int anchor_call) } int -filter_consistent(struct pfctl_rule *r, int anchor_call) +filter_consistent(struct pfctl_rule *r) { int problems = 0; @@ -5456,6 +5434,7 @@ process_tabledef(char *name, struct table_opts *opts, int popts) { struct pfr_buffer ab; struct node_tinit *ti; + struct pfr_uktable *ukt; unsigned long maxcount; size_t s = sizeof(maxcount); @@ -5488,9 +5467,23 @@ process_tabledef(char *name, struct table_opts *opts, int popts) else if (pf->opts & PF_OPT_VERBOSE) fprintf(stderr, "%s:%d: skipping duplicate table checks" " for <%s>\n", file->name, yylval.lineno, name); + /* + * postpone definition of non-root tables to moment + * when path is fully resolved. + */ + if (pf->asd > 0) { + ukt = calloc(1, sizeof(struct pfr_uktable)); + if (ukt == NULL) { + DBGPRINT( + "%s:%d: not enough memory for <%s>\n", file->name, + yylval.lineno, name); + goto _error; + } + } else + ukt = NULL; if (!(pf->opts & PF_OPT_NOACTION) && pfctl_define_table(name, opts->flags, opts->init_addr, - pf->anchor->path, &ab, pf->anchor->ruleset.tticket)) { + pf->anchor->path, &ab, pf->anchor->ruleset.tticket, ukt)) { if (sysctlbyname("net.pf.request_maxcount", &maxcount, &s, NULL, 0) == -1) @@ -5506,6 +5499,28 @@ process_tabledef(char *name, struct table_opts *opts, int popts) goto _error; } + + if (ukt != NULL) { + ukt->pfrukt_init_addr = opts->init_addr; + if (RB_INSERT(pfr_ktablehead, &pfr_ktables, + &ukt->pfrukt_kt) != NULL) { + /* + * I think this should not happen, because + * pfctl_define_table() above does the same check + * effectively. + */ + DBGPRINT( + "%s:%d table %s already exists in %s\n", + file->name, yylval.lineno, + ukt->pfrukt_name, pf->anchor->path); + free(ukt); + goto _error; + } + DBGPRINT("%s %s@%s inserted to tree\n", + __func__, ukt->pfrukt_name, pf->anchor->path); + } else + DBGPRINT("%s ukt is null\n", __func__); + pf->tdirty = 1; pfr_buf_clear(&ab); return (0); @@ -6302,7 +6317,7 @@ expand_rule(struct pfctl_rule *r, bool keeprule, 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_if *rcv, struct node_icmp *icmp_types, const char *anchor_call) + struct node_if *rcv, struct node_icmp *icmp_types) { sa_family_t af = r->af; int added = 0, error = 0; @@ -6469,11 +6484,11 @@ expand_rule(struct pfctl_rule *r, bool keeprule, error += check_binat_redirspec(src_host, r, af); } - if (rule_consistent(r, anchor_call[0]) < 0 || error) + if (rule_consistent(r) < 0 || error) yyerror("skipping rule due to errors"); else { r->nr = pf->astack[pf->asd]->match++; - pfctl_append_rule(pf, r, anchor_call); + pfctl_append_rule(pf, r); added++; } @@ -6489,8 +6504,7 @@ expand_rule(struct pfctl_rule *r, bool keeprule, expand_rule(&rdr_rule, true, interface, NULL, rdr_redirspec, NULL, proto, src_os, dst_host, dst_port, - rdr_dst_host, src_port, uid, gid, rcv, icmp_type, - ""); + rdr_dst_host, src_port, uid, gid, rcv, icmp_type); } if (osrch && src_host->addr.type == PF_ADDR_DYNIFTL) { @@ -6684,6 +6698,7 @@ lookup(char *s) { "no-route", NOROUTE}, { "no-sync", NOSYNC}, { "on", ON}, + { "once", ONCE}, { "optimization", OPTIMIZATION}, { "os", OS}, { "out", OUT}, @@ -7277,6 +7292,61 @@ mv_eth_rules(struct pfctl_eth_ruleset *src, struct pfctl_eth_ruleset *dst) } void +mv_tables(struct pfctl *pf, struct pfr_ktablehead *ktables, + struct pfctl_anchor *a, struct pfctl_anchor *alast) +{ + struct pfr_ktable *kt, *kt_safe; + char new_path[PF_ANCHOR_MAXPATH]; + char *path_cut; + int sz; + struct pfr_uktable *ukt; + SLIST_HEAD(, pfr_uktable) ukt_list; + + /* + * Here we need to rename anchor path from temporal names such as + * _1/_2/foo to _1/bar/foo etc. + * + * This also means we need to remove and insert table to ktables + * tree as anchor path is being updated. + */ + SLIST_INIT(&ukt_list); + DBGPRINT("%s [ %s ] (%s)\n", __func__, a->path, alast->path); + RB_FOREACH_SAFE(kt, pfr_ktablehead, ktables, kt_safe) { + path_cut = strstr(kt->pfrkt_anchor, alast->path); + if (path_cut != NULL) { + path_cut += strlen(alast->path); + if (*path_cut) + sz = snprintf(new_path, sizeof (new_path), + "%s%s", a->path, path_cut); + else + sz = snprintf(new_path, sizeof (new_path), + "%s", a->path); + if (sz >= sizeof (new_path)) + errx(1, "new path is too long for %s@%s\n", + kt->pfrkt_name, kt->pfrkt_anchor); + + DBGPRINT("%s %s@%s -> %s@%s\n", __func__, + kt->pfrkt_name, kt->pfrkt_anchor, + kt->pfrkt_name, new_path); + RB_REMOVE(pfr_ktablehead, ktables, kt); + strlcpy(kt->pfrkt_anchor, new_path, + sizeof(kt->pfrkt_anchor)); + SLIST_INSERT_HEAD(&ukt_list, (struct pfr_uktable *)kt, + pfrukt_entry); + } + } + + while ((ukt = SLIST_FIRST(&ukt_list)) != NULL) { + SLIST_REMOVE_HEAD(&ukt_list, pfrukt_entry); + if (RB_INSERT(pfr_ktablehead, ktables, + (struct pfr_ktable *)ukt) != NULL) + errx(1, "%s@%s exists already\n", + ukt->pfrukt_name, + ukt->pfrukt_anchor); + } +} + +void decide_address_family(struct node_host *n, sa_family_t *af) { if (*af != 0 || n == NULL) @@ -7485,7 +7555,7 @@ parseport(char *port, struct range *r, int extensions) } int -pfctl_load_anchors(int dev, struct pfctl *pf, struct pfr_buffer *trans) +pfctl_load_anchors(int dev, struct pfctl *pf) { struct loadanchors *la; @@ -7494,7 +7564,7 @@ pfctl_load_anchors(int dev, struct pfctl *pf, struct pfr_buffer *trans) 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) + la->anchorname, pf->trans) == -1) return (-1); } @@ -7642,6 +7712,14 @@ node_mac_from_string_mask(const char *str, const char *mask) int filteropts_to_rule(struct pfctl_rule *r, struct filter_opts *opts) { + if (opts->marker & FOM_ONCE) { + if ((r->action != PF_PASS && r->action != PF_DROP) || r->anchor) { + yyerror("'once' only applies to pass/block rules"); + return (1); + } + r->rule_flag |= PFRULE_ONCE; + } + r->keep_state = opts->keep.action; r->pktrate.limit = opts->pktrate.limit; r->pktrate.seconds = opts->pktrate.seconds; @@ -7729,3 +7807,73 @@ filteropts_to_rule(struct pfctl_rule *r, struct filter_opts *opts) return (0); } + +static bool +pfctl_setup_anchor(struct pfctl_rule *r, struct pfctl *pf, char *anchorname) +{ + char *p; + + if (pf->astack[pf->asd + 1]) { + if (anchorname && strchr(anchorname, '/') != NULL) { + free(anchorname); + yyerror("anchor paths containing '/' " + "cannot be used for inline anchors."); + return (false); + } + + /* Move inline rules into relative location. */ + pfctl_anchor_setup(r, + &pf->astack[pf->asd]->ruleset, + anchorname ? anchorname : 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); + return (false); + } + mv_rules(&pf->alast->ruleset, + &r->anchor->ruleset); + mv_tables(pf, &pfr_ktables, r->anchor, pf->alast); + } + pf_remove_if_empty_ruleset(&pf->alast->ruleset); + pf->alast = r->anchor; + } else { + if (! anchorname) { + yyerror("anchors without explicit " + "rules must specify a name"); + return (false); + } + /* + * Don't make non-brace anchors part of the main anchor pool. + */ + if ((r->anchor = calloc(1, sizeof(*r->anchor))) == NULL) { + err(1, "anchorrule: calloc"); + } + pf_init_ruleset(&r->anchor->ruleset); + r->anchor->ruleset.anchor = r->anchor; + if (strlcpy(r->anchor->path, anchorname, + sizeof(r->anchor->path)) >= sizeof(r->anchor->path)) { + errx(1, "anchorrule: strlcpy"); + } + if ((p = strrchr(anchorname, '/')) != NULL) { + if (strlen(p) == 1) { + yyerror("anchorrule: bad anchor name %s", + anchorname); + return (false); + } + } else + p = anchorname; + if (strlcpy(r->anchor->name, p, + sizeof(r->anchor->name)) >= sizeof(r->anchor->name)) { + errx(1, "anchorrule: strlcpy"); + } + } + + return (true); +} diff --git a/sbin/pfctl/pfctl.8 b/sbin/pfctl/pfctl.8 index 5a74a8fd3444..58de54cdf923 100644 --- a/sbin/pfctl/pfctl.8 +++ b/sbin/pfctl/pfctl.8 @@ -24,7 +24,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd August 5, 2025 +.Dd August 28, 2025 .Dt PFCTL 8 .Os .Sh NAME @@ -115,8 +115,9 @@ Apply flags .Fl f , .Fl F , .Fl s , +.Fl T , and -.Fl T +.Fl z only to the rules in the specified .Ar anchor . In addition to the main ruleset, @@ -211,31 +212,31 @@ Flush the filter parameters specified by .Ar modifier (may be abbreviated): .Pp -.Bl -tag -width xxxxxxxxxxxx -compact -.It Fl F Cm nat +.Bl -tag -width xxxxxxxxx -compact +.It Cm nat Flush the NAT rules. -.It Fl F Cm queue +.It Cm queue Flush the queue rules. -.It Fl F Cm ethernet +.It Cm ethernet Flush the Ethernet filter rules. -.It Fl F Cm rules +.It Cm rules Flush the filter rules. -.It Fl F Cm states +.It Cm states Flush the state table (NAT and filter). -.It Fl F Cm Sources +.It Cm Sources Flush the source tracking table. -.It Fl F Cm info +.It Cm info Flush the filter information (statistics that are not bound to rules). -.It Fl F Cm Tables +.It Cm Tables Flush the tables. -.It Fl F Cm osfp +.It Cm osfp Flush the passive operating system fingerprints. -.It Fl F Cm Reset +.It Cm Reset Reset limits, timeouts and other options back to default settings. See the OPTIONS section in .Xr pf.conf 5 for details. -.It Fl F Cm all +.It Cm all Flush all of the above. .El .Pp @@ -401,13 +402,13 @@ Other rules and options are ignored. .It Fl o Ar level Control the ruleset optimizer, overriding any rule file settings. .Pp -.Bl -tag -width xxxxxxxxxxxx -compact -.It Fl o Cm none +.Bl -tag -width xxxxxxxxx -compact +.It Cm none Disable the ruleset optimizer. -.It Fl o Cm basic +.It Cm basic Enable basic ruleset optimizations. This is the default behaviour. -.It Fl o Cm profile +.It Cm profile Enable basic ruleset optimizations with profiling. .El .Pp @@ -437,10 +438,10 @@ Show the filter parameters specified by .Ar modifier (may be abbreviated): .Pp -.Bl -tag -width xxxxxxxxxxxxx -compact -.It Fl s Cm nat +.Bl -tag -width xxxxxxxxxxx -compact +.It Cm nat Show the currently loaded NAT rules. -.It Fl s Cm queue +.It Cm queue Show the currently loaded queue rules. When used together with .Fl v , @@ -450,18 +451,25 @@ When used together with .Nm will loop and show updated queue statistics every five seconds, including measured bandwidth and packets per second. -.It Fl s Cm ether +.It Cm ether Show the currently loaded Ethernet rules. When used together with .Fl v , the per-rule statistics (number of evaluations, packets, and bytes) are also shown. -.It Fl s Cm rules +.It Cm rules Show the currently loaded filter rules. When used together with .Fl v , the per-rule statistics (number of evaluations, packets, and bytes) are also shown. +When used together with +.Fl g +or +.Fl vv , +expired rules +.Pq marked as Dq # expired +are also shown. Note that the .Dq skip step optimization done automatically by the kernel @@ -469,7 +477,7 @@ will skip evaluation of rules where possible. Packets passed statefully are counted in the rule that created the state (even though the rule is not evaluated more than once for the entire connection). -.It Fl s Cm Anchors +.It Cm Anchors Show the currently loaded anchors directly attached to the main ruleset. If .Fl a Ar anchor @@ -480,11 +488,11 @@ If .Fl v is specified, all anchors attached under the target anchor will be displayed recursively. -.It Fl s Cm states +.It Cm states Show the contents of the state table. -.It Fl s Cm Sources +.It Cm Sources Show the contents of the source tracking table. -.It Fl s Cm info +.It Cm info Show filter information (statistics and counters). When used together with .Fl v , @@ -492,21 +500,21 @@ source tracking statistics, the firewall's 32-bit hostid number and the main ruleset's MD5 checksum for use with .Xr pfsync 4 are also shown. -.It Fl s Cm Running +.It Cm Running Show the running status and provide a non-zero exit status when disabled. -.It Fl s Cm labels +.It Cm labels Show per-rule statistics (label, evaluations, packets total, bytes total, packets in, bytes in, packets out, bytes out, state creations) of filter rules with labels, useful for accounting. -.It Fl s Cm timeouts +.It Cm timeouts Show the current global timeouts. -.It Fl s Cm memory +.It Cm memory Show the current pool memory hard limits. -.It Fl s Cm Tables +.It Cm Tables Show the list of tables. -.It Fl s Cm osfp +.It Cm osfp Show the list of operating system fingerprints. -.It Fl s Cm Interfaces +.It Cm Interfaces Show the list of interfaces and interface groups available to PF. When used together with .Fl v , @@ -516,7 +524,7 @@ When used together with interface statistics are also shown. .Fl i can be used to select an interface or a group of interfaces. -.It Fl s Cm all +.It Cm all Show all of the above, except for the lists of interfaces and operating system fingerprints. .El @@ -571,38 +579,38 @@ Specify the .Ar table . Commands include: .Pp -.Bl -tag -width "-T expire number" -compact -.It Fl T Cm add +.Bl -tag -width "expire number" -compact +.It Cm add Add one or more addresses to a table. Automatically create a persistent table if it does not exist. -.It Fl T Cm delete +.It Cm delete Delete one or more addresses from a table. -.It Fl T Cm expire Ar number +.It Cm expire Ar number Delete addresses which had their statistics cleared more than .Ar number seconds ago. For entries which have never had their statistics cleared, .Ar number refers to the time they were added to the table. -.It Fl T Cm flush +.It Cm flush Flush all addresses in a table. -.It Fl T Cm kill +.It Cm kill Kill a table. -.It Fl T Cm replace +.It Cm replace Replace the addresses of the table. Automatically create a persistent table if it does not exist. -.It Fl T Cm show +.It Cm show Show the content (addresses) of a table. -.It Fl T Cm test +.It Cm test Test if the given addresses match a table. -.It Fl T Cm zero Op Ar address ... +.It Cm zero Op Ar address ... Clear all the statistics of a table, or only for specified addresses. -.It Fl T Cm reset +.It Cm reset Clear statistics only for addresses with non-zero statistics. Addresses with counter values at zero and their .Dq Cleared timestamp are left untouched. -.It Fl T Cm load +.It Cm load Load only the table definitions from .Xr pf.conf 5 . This is used in conjunction with the diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c index 2c12387ee388..ed317495c2e0 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -110,6 +110,8 @@ 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_read_limits(struct pfctl_handle *); +void pfctl_restore_limits(void); void pfctl_debug(int, u_int32_t, int); int pfctl_test_altqsupport(int, int); int pfctl_show_anchors(int, int, char *); @@ -189,6 +191,8 @@ static const struct { { NULL, 0 } }; +static unsigned int limit_curr[PF_LIMIT_MAX]; + struct pf_hint { const char *name; int timeout; @@ -1138,6 +1142,9 @@ pfctl_print_rule_counters(struct pfctl_rule *rule, int opts) printf(" [ queue: qname=%s qid=%u pqname=%s pqid=%u ]\n", rule->qname, rule->qid, rule->pqname, rule->pqid); + if (rule->rule_flag & PFRULE_EXPIRED) + printf(" [ Expired: %lld secs ago ]\n", + (long long)(time(NULL) - rule->exptime)); } if (opts & PF_OPT_VERBOSE) { printf(" [ Evaluations: %-8llu Packets: %-8llu " @@ -1308,7 +1315,6 @@ pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, struct pfctl_rule rule; char anchor_call[MAXPATHLEN]; u_int32_t nr, header = 0; - int rule_numbers = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); int numeric = opts & PF_OPT_NUMERIC; int len = strlen(path), ret = 0; char *npath, *p; @@ -1406,8 +1412,14 @@ pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, case PFCTL_SHOW_RULES: if (rule.label[0][0] && (opts & PF_OPT_SHOWALL)) labels = 1; - print_rule(&rule, anchor_call, rule_numbers, numeric); - printf("\n"); + print_rule(&rule, anchor_call, opts, numeric); + /* + * Do not print newline, when we have not + * printed expired rule. + */ + if (!(rule.rule_flag & PFRULE_EXPIRED) || + (opts & (PF_OPT_VERBOSE2|PF_OPT_DEBUG))) + printf("\n"); pfctl_print_rule_counters(&rule, opts); break; case PFCTL_SHOW_NOTHING: @@ -1479,7 +1491,7 @@ pfctl_show_rules(int dev, char *path, int opts, enum pfctl_show format, if (rule.label[0][0] && (opts & PF_OPT_SHOWALL)) labels = 1; INDENT(depth, !(opts & PF_OPT_VERBOSE)); - print_rule(&rule, anchor_call, rule_numbers, numeric); + print_rule(&rule, anchor_call, opts, numeric); /* * If this is a 'unnamed' brace notation @@ -1781,6 +1793,31 @@ pfctl_show_limits(int dev, int opts) } void +pfctl_read_limits(struct pfctl_handle *h) +{ + int i; + + for (i = 0; pf_limits[i].name; i++) { + if (pfctl_get_limit(h, i, &limit_curr[i])) + err(1, "DIOCGETLIMIT"); + } +} + +void +pfctl_restore_limits(void) +{ + int i; + + if (pfh == NULL) + return; + + for (i = 0; pf_limits[i].name; i++) { + if (pfctl_set_limit(pfh, i, limit_curr[i])) + warn("DIOCSETLIMIT (%s)", pf_limits[i].name); + } +} + +void pfctl_show_creators(int opts) { int ret; @@ -1823,14 +1860,12 @@ pfctl_init_rule(struct pfctl_rule *r) TAILQ_INIT(&(r->route.list)); } -int -pfctl_append_rule(struct pfctl *pf, struct pfctl_rule *r, - const char *anchor_call) +void +pfctl_append_rule(struct pfctl *pf, struct pfctl_rule *r) { u_int8_t rs_num; struct pfctl_rule *rule; struct pfctl_ruleset *rs; - char *p; rs_num = pf_get_ruleset_number(r->action); if (rs_num == PF_RULESET_MAX) @@ -1838,29 +1873,6 @@ pfctl_append_rule(struct pfctl *pf, struct pfctl_rule *r, 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)); @@ -1872,7 +1884,6 @@ pfctl_append_rule(struct pfctl *pf, struct pfctl_rule *r, pfctl_move_pool(&r->route, &rule->route); TAILQ_INSERT_TAIL(rs->rules[rs_num].active.ptr, rule, entries); - return (0); } int @@ -2065,6 +2076,41 @@ pfctl_load_eth_rule(struct pfctl *pf, char *path, struct pfctl_eth_rule *r, return (0); } +static int +pfctl_load_tables(struct pfctl *pf, char *path, struct pfctl_anchor *a, + int rs_num) +{ + struct pfr_ktable *kt, *ktw; + struct pfr_uktable *ukt; + char anchor_path[PF_ANCHOR_MAXPATH]; + int e; + + RB_FOREACH_SAFE(kt, pfr_ktablehead, &pfr_ktables, ktw) { + if (strcmp(kt->pfrkt_anchor, a->path) != 0) + continue; + + if (path != NULL && *path) { + strlcpy(anchor_path, kt->pfrkt_anchor, + sizeof(anchor_path)); + snprintf(kt->pfrkt_anchor, PF_ANCHOR_MAXPATH, "%s/%s", + path, anchor_path); + } + ukt = (struct pfr_uktable *)kt; + e = pfr_ina_define(&ukt->pfrukt_t, ukt->pfrukt_addrs.pfrb_caddr, + ukt->pfrukt_addrs.pfrb_size, NULL, NULL, + pf->anchor->ruleset.tticket, + ukt->pfrukt_init_addr ? PFR_FLAG_ADDRSTOO : 0); + if (e != 0) + err(1, "%s pfr_ina_define() %s@%s", __func__, + kt->pfrkt_name, kt->pfrkt_anchor); + RB_REMOVE(pfr_ktablehead, &pfr_ktables, kt); + pfr_buf_clear(&ukt->pfrukt_addrs); + free(ukt); + } + + return (0); +} + int pfctl_load_ruleset(struct pfctl *pf, char *path, struct pfctl_ruleset *rs, int rs_num, int depth) @@ -2113,6 +2159,8 @@ pfctl_load_ruleset(struct pfctl *pf, char *path, struct pfctl_ruleset *rs, if ((error = pfctl_load_ruleset(pf, path, &r->anchor->ruleset, rs_num, depth + 1))) goto error; + if ((error = pfctl_load_tables(pf, path, r->anchor, rs_num))) + goto error; } else if (pf->opts & PF_OPT_VERBOSE) printf("\n"); free(r); @@ -2135,15 +2183,20 @@ 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; + uint32_t ticket; char anchor[PF_ANCHOR_NAME_SIZE]; int len = strlen(path); int error; bool was_present; /* set up anchor before adding to path for anchor_call */ - if ((pf->opts & PF_OPT_NOACTION) == 0) + if ((pf->opts & PF_OPT_NOACTION) == 0) { + if (pf->trans == NULL) + errx(1, "pfctl_load_rule: no transaction"); ticket = pfctl_get_ticket(pf->trans, rs_num, path); + if (rs_num == PF_RULESET_FILTER) + pf->anchor->ruleset.tticket = ticket; + } if (strlcpy(anchor, path, sizeof(anchor)) >= sizeof(anchor)) errx(1, "pfctl_load_rule: strlcpy"); @@ -2245,6 +2298,8 @@ pfctl_rules(int dev, char *filename, int opts, int optimize, RB_INIT(&pf_anchors); memset(&pf_main_anchor, 0, sizeof(pf_main_anchor)); pf_init_ruleset(&pf_main_anchor.ruleset); + memset(&pf, 0, sizeof(pf)); + memset(&trs, 0, sizeof(trs)); pf_main_anchor.ruleset.anchor = &pf_main_anchor; memset(&pf_eth_main_anchor, 0, sizeof(pf_eth_main_anchor)); @@ -2254,6 +2309,7 @@ pfctl_rules(int dev, char *filename, int opts, int optimize, if (trans == NULL) { bzero(&buf, sizeof(buf)); buf.pfrb_type = PFRB_TRANS; + pf.trans = &buf; t = &buf; osize = 0; } else { @@ -2364,7 +2420,7 @@ pfctl_rules(int dev, char *filename, int opts, int optimize, if (trans == NULL) { /* process "load anchor" directives */ - if (pfctl_load_anchors(dev, &pf, t) == -1) + if (pfctl_load_anchors(dev, &pf) == -1) ERRX("load anchors"); if ((opts & PF_OPT_NOACTION) == 0) { @@ -2445,8 +2501,14 @@ pfctl_init_options(struct pfctl *pf) 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->limit[PF_LIMIT_SRC_NODES] = (limit_curr[PF_LIMIT_SRC_NODES] == 0) ? + PFSNODE_HIWAT : limit_curr[PF_LIMIT_SRC_NODES]; + pf->limit[PF_LIMIT_TABLE_ENTRIES] = + (limit_curr[PF_LIMIT_TABLE_ENTRIES] == 0) ? + PFR_KENTRY_HIWAT : limit_curr[PF_LIMIT_TABLE_ENTRIES]; + pf->limit[PF_LIMIT_ANCHORS] = (limit_curr[PF_LIMIT_ANCHORS] == 0) ? + PF_ANCHOR_HIWAT : limit_curr[PF_LIMIT_ANCHORS]; pf->debug = PF_DEBUG_URGENT; pf->reassemble = 0; @@ -2547,6 +2609,9 @@ pfctl_apply_limit(struct pfctl *pf, const char *opt, unsigned int limit) if (pf->opts & PF_OPT_VERBOSE) printf("set limit %s %d\n", opt, limit); + if ((pf->opts & PF_OPT_NOACTION) == 0) + pfctl_load_options(pf); + return (0); } @@ -3410,6 +3475,11 @@ main(int argc, char *argv[]) if (pfh == NULL) err(1, "Failed to open netlink"); + if ((opts & PF_OPT_NOACTION) == 0) { + pfctl_read_limits(pfh); + atexit(pfctl_restore_limits); + } + if (opts & PF_OPT_DISABLE) if (pfctl_disable(dev, opts)) exit_val = 1; @@ -3515,6 +3585,12 @@ main(int argc, char *argv[]) } if (clearopt != NULL) { + int mnr; + + /* Check if anchor exists. */ + if ((pfctl_get_rulesets(pfh, anchorname, &mnr)) == ENOENT) + errx(1, "No such anchor %s", anchorname); + switch (*clearopt) { case 'e': pfctl_flush_eth_rules(dev, opts, anchorname); @@ -3653,7 +3729,18 @@ main(int argc, char *argv[]) } } - exit(exit_val); + /* + * prevent pfctl_restore_limits() exit handler from restoring + * pf(4) options settings on successful exit. + */ + if (exit_val == 0) { + close(dev); + dev = -1; + pfctl_close(pfh); + pfh = NULL; + } + + return (exit_val); } char * diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h index afecc78086e0..136f51ea08f9 100644 --- a/sbin/pfctl/pfctl.h +++ b/sbin/pfctl/pfctl.h @@ -36,6 +36,12 @@ #include <libpfctl.h> +#ifdef PFCTL_DEBUG +#define DBGPRINT(...) fprintf(stderr, __VA_ARGS__) +#else +#define DBGPRINT(...) (void)(0) +#endif + extern struct pfctl_handle *pfh; struct pfctl; @@ -55,6 +61,41 @@ struct pfr_buffer { (var) != NULL; \ (var) = pfr_buf_next((buf), (var))) +RB_HEAD(pfr_ktablehead, pfr_ktable); +struct pfr_ktable { + struct pfr_tstats pfrkt_ts; + RB_ENTRY(pfr_ktable) pfrkt_tree; + SLIST_ENTRY(pfr_ktable) pfrkt_workq; + struct radix_node_head *pfrkt_ip4; + struct radix_node_head *pfrkt_ip6; + struct pfr_ktable *pfrkt_shadow; + struct pfr_ktable *pfrkt_root; + struct pf_kruleset *pfrkt_rs; + long pfrkt_larg; + int pfrkt_nflags; +}; +#define pfrkt_t pfrkt_ts.pfrts_t +#define pfrkt_name pfrkt_t.pfrt_name +#define pfrkt_anchor pfrkt_t.pfrt_anchor +#define pfrkt_ruleset pfrkt_t.pfrt_ruleset +#define pfrkt_flags pfrkt_t.pfrt_flags +#define pfrkt_cnt pfrkt_kts.pfrkts_cnt +#define pfrkt_refcnt pfrkt_kts.pfrkts_refcnt +#define pfrkt_tzero pfrkt_kts.pfrkts_tzero + +struct pfr_uktable { + struct pfr_ktable pfrukt_kt; + struct pfr_buffer pfrukt_addrs; + int pfrukt_init_addr; + SLIST_ENTRY(pfr_uktable) pfrukt_entry; +}; + +#define pfrukt_t pfrukt_kt.pfrkt_ts.pfrts_t +#define pfrukt_name pfrukt_kt.pfrkt_t.pfrt_name +#define pfrukt_anchor pfrukt_kt.pfrkt_t.pfrt_anchor + +extern struct pfr_ktablehead pfr_ktables; + struct pfr_anchoritem { SLIST_ENTRY(pfr_anchoritem) pfra_sle; char *pfra_anchorname; @@ -62,7 +103,6 @@ struct pfr_anchoritem { SLIST_HEAD(pfr_anchors, pfr_anchoritem); -int pfr_get_fd(void); int pfr_add_table(struct pfr_table *, int *, int); int pfr_del_table(struct pfr_table *, int *, int); int pfr_get_tables(struct pfr_table *, struct pfr_table *, int *, int); @@ -85,7 +125,6 @@ int pfr_buf_load(struct pfr_buffer *, char *, int, int (*)(struct pfr_buffer *, char *, int, int), int); char *pf_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_do_clear_tables(const char *, int); diff --git a/sbin/pfctl/pfctl_optimize.c b/sbin/pfctl/pfctl_optimize.c index 1d2a60555f19..2d16bbd22b39 100644 --- a/sbin/pfctl/pfctl_optimize.c +++ b/sbin/pfctl/pfctl_optimize.c @@ -1331,7 +1331,8 @@ again: if (pfctl_define_table(tbl->pt_name, PFR_TFLAG_CONST, 1, - pf->astack[0]->path, tbl->pt_buf, pf->astack[0]->ruleset.tticket)) { + pf->astack[0]->path, tbl->pt_buf, pf->astack[0]->ruleset.tticket, + NULL)) { warn("failed to create table %s in %s", tbl->pt_name, pf->astack[0]->name); return (1); diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c index ce58e0636022..b8531067d3f6 100644 --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -389,9 +389,11 @@ print_flags(uint16_t f) 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) + sa_family_t af, u_int8_t proto, int opts, int numeric) { char buf[PF_OSFP_LEN*3]; + int verbose = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); + if (src->addr.type == PF_ADDR_ADDRMASK && dst->addr.type == PF_ADDR_ADDRMASK && PF_AZERO(&src->addr.v.a.addr, AF_INET6) && @@ -614,6 +616,20 @@ print_status(struct pfctl_status *s, struct pfctl_syncookies *cookies, int opts) printf("%14s\n", ""); } } + if (opts & PF_OPT_VERBOSE) { + printf("Fragments\n"); + printf(" %-25s %14ju %14s\n", "current entries", + s->fragments, ""); + TAILQ_FOREACH(c, &s->ncounters, entry) { + printf(" %-25s %14ju ", c->name, + c->counter); + if (runtime > 0) + printf("%14.1f/s\n", + (double)c->counter / (double)runtime); + else + printf("%14s\n", ""); + } + } printf("Counters\n"); TAILQ_FOREACH(c, &s->counters, entry) { printf(" %-25s %14ju ", c->name, c->counter); @@ -840,34 +856,39 @@ print_eth_rule(struct pfctl_eth_rule *r, const char *anchor_call, } void -print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numeric) +print_rule(struct pfctl_rule *r, const char *anchor_call, int opts, int numeric) { static const char *actiontypes[] = { "pass", "block", "scrub", "no scrub", "nat", "no nat", "binat", "no binat", "rdr", "no rdr", - "", "", "match"}; + "synproxy drop", "defer", "match", "af-rt", "route-to" }; static const char *anchortypes[] = { "anchor", "anchor", "anchor", "anchor", "nat-anchor", "nat-anchor", "binat-anchor", "binat-anchor", "rdr-anchor", "rdr-anchor" }; - int i, ropts; + int i, ropts; + int verbose = opts & (PF_OPT_VERBOSE2 | PF_OPT_DEBUG); char *p; + if ((r->rule_flag & PFRULE_EXPIRED) && (!verbose)) + return; + if (verbose) printf("@%d ", r->nr); - if (r->action == PF_MATCH) - printf("match"); - else if (r->action > PF_NORDR) - printf("action(%d)", r->action); - else if (anchor_call[0]) { - p = strrchr(anchor_call, '/'); - if (p ? p[1] == '_' : anchor_call[0] == '_') - printf("%s", anchortypes[r->action]); - else - printf("%s \"%s\"", anchortypes[r->action], - anchor_call); + if (anchor_call[0]) { + if (r->action >= nitems(anchortypes)) { + printf("anchor(%d)", r->action); + } else { + p = strrchr(anchor_call, '/'); + if (p ? p[1] == '_' : 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 >= nitems(actiontypes)) + printf("action(%d)", r->action); + else + printf("%s", actiontypes[r->action]); } if (r->action == PF_DROP) { if (r->rule_flag & PFRULE_RETURN) @@ -970,7 +991,7 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer printf(" proto %u", r->proto); } print_fromto(&r->src, r->os_fingerprint, &r->dst, r->af, r->proto, - verbose, numeric); + opts, numeric); if (r->rcv_ifname[0]) printf(" %sreceived-on %s", r->rcvifnot ? "!" : "", r->rcv_ifname); @@ -1219,6 +1240,8 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer printf(" %s %d", r->free_flags & PFRULE_DN_IS_PIPE ? "dnpipe" : "dnqueue", r->dnpipe); + if (r->rule_flag & PFRULE_ONCE) + printf(" once"); if (r->qname[0] && r->pqname[0]) printf(" queue(%s, %s)", r->qname, r->pqname); else if (r->qname[0]) @@ -1271,6 +1294,13 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer r->rdr.proxy_port[1], PF_RDR); } } + + if (r->rule_flag & PFRULE_EXPIRED) { + printf(" # expired"); + + if (r->exptime != 0) + printf(" %s", ctime(&r->exptime)); + } } void diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h index 58d3abc36691..44ddfb45fbe1 100644 --- a/sbin/pfctl/pfctl_parser.h +++ b/sbin/pfctl/pfctl_parser.h @@ -36,6 +36,8 @@ #include <libpfctl.h> +#include <pfctl.h> + #define PF_OSFP_FILE "/etc/pf.os" #define PF_OPT_DISABLE 0x00001 @@ -90,6 +92,7 @@ struct pfctl { struct pfioc_queue *pqueue; struct pfr_buffer *trans; struct pfctl_anchor *anchor, *alast; + struct pfr_ktablehead pfr_ktlast; int eth_nr; struct pfctl_eth_anchor *eanchor, *ealast; struct pfctl_eth_anchor *eastack[PFCTL_ANCHOR_STACK_DEPTH]; @@ -277,13 +280,15 @@ struct pf_opt_rule { TAILQ_HEAD(pf_opt_queue, pf_opt_rule); +struct pfr_uktable; + void copy_satopfaddr(struct pf_addr *, struct sockaddr *); int pfctl_rules(int, char *, int, int, char *, struct pfr_buffer *); int pfctl_optimize_ruleset(struct pfctl *, struct pfctl_ruleset *); void pfctl_init_rule(struct pfctl_rule *r); -int pfctl_append_rule(struct pfctl *, struct pfctl_rule *, const char *); +void pfctl_append_rule(struct pfctl *, struct pfctl_rule *); int pfctl_append_eth_rule(struct pfctl *, struct pfctl_eth_rule *, const char *); int pfctl_add_altq(struct pfctl *, struct pf_altq *); @@ -303,7 +308,7 @@ int pfctl_cfg_syncookies(struct pfctl *, uint8_t, struct pfctl_watermarks *); int parse_config(char *, struct pfctl *); int parse_flags(char *); -int pfctl_load_anchors(int, struct pfctl *, struct pfr_buffer *); +int pfctl_load_anchors(int, struct pfctl *); void print_pool(struct pfctl_pool *, u_int16_t, u_int16_t, int); void print_src_node(struct pfctl_src_node *, int); @@ -324,7 +329,7 @@ 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); + u_int32_t, struct pfr_uktable *); void pfctl_clear_fingerprints(int, int); int pfctl_file_fingerprints(int, int, const char *); @@ -380,5 +385,8 @@ struct node_host *host(const char *, int); int append_addr(struct pfr_buffer *, char *, int, int); int append_addr_host(struct pfr_buffer *, struct node_host *, int, int); +int pfr_ktable_compare(struct pfr_ktable *, + struct pfr_ktable *); +RB_PROTOTYPE(pfr_ktablehead, pfr_ktable, pfrkt_tree, pfr_ktable_compare); #endif /* _PFCTL_PARSER_H_ */ diff --git a/sbin/pfctl/pfctl_radix.c b/sbin/pfctl/pfctl_radix.c index 398c5e998330..98f907738d95 100644 --- a/sbin/pfctl/pfctl_radix.c +++ b/sbin/pfctl/pfctl_radix.c @@ -48,6 +48,7 @@ #include <err.h> #include "pfctl.h" +#include "pfctl_parser.h" #define BUF_SIZE 256 @@ -55,6 +56,19 @@ extern int dev; static int pfr_next_token(char buf[BUF_SIZE], FILE *); +struct pfr_ktablehead pfr_ktables = { 0 }; +RB_GENERATE(pfr_ktablehead, pfr_ktable, pfrkt_tree, pfr_ktable_compare); + +int +pfr_ktable_compare(struct pfr_ktable *p, struct pfr_ktable *q) +{ + int d; + + if ((d = strncmp(p->pfrkt_name, q->pfrkt_name, PF_TABLE_NAME_SIZE))) + return (d); + return (strcmp(p->pfrkt_anchor, q->pfrkt_anchor)); +} + static void pfr_report_error(struct pfr_table *tbl, struct pfioc_table *io, const char *err) @@ -256,6 +270,7 @@ pfr_ina_define(struct pfr_table *tbl, struct pfr_addr *addr, int size, struct pfioc_table io; if (tbl == NULL || size < 0 || (size && addr == NULL)) { + DBGPRINT("%s %p %d %p\n", __func__, tbl, size, addr); errno = EINVAL; return (-1); } diff --git a/sbin/pfctl/pfctl_table.c b/sbin/pfctl/pfctl_table.c index 0845f765a063..4955e1791fd7 100644 --- a/sbin/pfctl/pfctl_table.c +++ b/sbin/pfctl/pfctl_table.c @@ -437,14 +437,19 @@ print_table(const struct pfr_table *ta, int verbose, int debug) int print_tstats(const struct pfr_tstats *ts, int debug) { - time_t time = ts->pfrts_tzero; - int dir, op; + time_t time = ts->pfrts_tzero; + int dir, op; + char *ct; if (!debug && !(ts->pfrts_flags & PFR_TFLAG_ACTIVE)) return (0); + ct = ctime(&time); print_table(&ts->pfrts_t, 1, debug); printf("\tAddresses: %d\n", ts->pfrts_cnt); - printf("\tCleared: %s", ctime(&time)); + if (ct) + printf("\tCleared: %s", ct); + else + printf("\tCleared: %lld\n", (long long)time); printf("\tReferences: [ Anchors: %-18d Rules: %-18d ]\n", ts->pfrts_refcnt[PFR_REFCNT_ANCHOR], ts->pfrts_refcnt[PFR_REFCNT_RULE]); @@ -543,12 +548,17 @@ nonzero_astats(struct pfr_astats *as) void print_astats(struct pfr_astats *as, int dns) { - time_t time = as->pfras_tzero; - int dir, op; + time_t time = as->pfras_tzero; + int dir, op; + char *ct; + ct = ctime(&time); print_addrx(&as->pfras_a, NULL, dns); - printf("\tCleared: %s", ctime(&time)); - if (as->pfras_a.pfra_fback == PFR_FB_NOCOUNT) + if (ct) + printf("\tCleared: %s", ct); + else + printf("\tCleared: %lld\n", (long long)time); + if (as->pfras_a.pfra_fback == PFR_FB_NOCOUNT) return; for (dir = 0; dir < PFR_DIR_MAX; dir++) for (op = 0; op < PFR_OP_ADDR_MAX; op++) @@ -560,19 +570,50 @@ print_astats(struct pfr_astats *as, int dns) int pfctl_define_table(char *name, int flags, int addrs, const char *anchor, - struct pfr_buffer *ab, u_int32_t ticket) + struct pfr_buffer *ab, u_int32_t ticket, struct pfr_uktable *ukt) { - struct pfr_table tbl; + struct pfr_table tbl_buf; + struct pfr_table *tbl; + + if (ukt == NULL) { + bzero(&tbl_buf, sizeof(tbl_buf)); + tbl = &tbl_buf; + } else { + if (ab->pfrb_size != 0) { + /* + * copy IP addresses which come with table from + * temporal buffer to buffer attached to table. + */ + ukt->pfrukt_addrs = *ab; + ab->pfrb_size = 0; + ab->pfrb_msize = 0; + ab->pfrb_caddr = NULL; + } else + memset(&ukt->pfrukt_addrs, 0, + sizeof(struct pfr_buffer)); + + tbl = &ukt->pfrukt_t; + } - bzero(&tbl, sizeof(tbl)); - if (strlcpy(tbl.pfrt_name, name, sizeof(tbl.pfrt_name)) >= - sizeof(tbl.pfrt_name) || strlcpy(tbl.pfrt_anchor, anchor, - sizeof(tbl.pfrt_anchor)) >= sizeof(tbl.pfrt_anchor)) - errx(1, "pfctl_define_table: strlcpy"); - tbl.pfrt_flags = flags; + if (strlcpy(tbl->pfrt_name, name, sizeof(tbl->pfrt_name)) >= + sizeof(tbl->pfrt_name) || + strlcpy(tbl->pfrt_anchor, anchor, sizeof(tbl->pfrt_anchor)) >= + sizeof(tbl->pfrt_anchor)) + errx(1, "%s: strlcpy", __func__); + tbl->pfrt_flags = flags; + DBGPRINT("%s %s@%s [%x]\n", __func__, tbl->pfrt_name, tbl->pfrt_anchor, + tbl->pfrt_flags); + + /* + * non-root anchors processed by parse.y are loaded to kernel later. + * Here we load tables, which are either created for root anchor + * or by 'pfctl -t ... -T ...' command. + */ + if (ukt != NULL) + return (0); - return pfr_ina_define(&tbl, ab->pfrb_caddr, ab->pfrb_size, NULL, - NULL, ticket, addrs ? PFR_FLAG_ADDRSTOO : 0); + return (pfr_ina_define(tbl, ab->pfrb_caddr, ab->pfrb_size, NULL, NULL, + ticket, addrs ? PFR_FLAG_ADDRSTOO : 0)); } void @@ -653,8 +694,9 @@ pfctl_show_ifaces(const char *filter, int opts) void print_iface(struct pfi_kif *p, int opts) { - time_t tzero = p->pfik_tzero; - int i, af, dir, act; + time_t tzero = p->pfik_tzero; + int i, af, dir, act; + char *ct; printf("%s", p->pfik_name); if (opts & PF_OPT_VERBOSE) { @@ -665,7 +707,11 @@ print_iface(struct pfi_kif *p, int opts) if (!(opts & PF_OPT_VERBOSE2)) return; - printf("\tCleared: %s", ctime(&tzero)); + ct = ctime(&tzero); + if (ct) + printf("\tCleared: %s", ct); + else + printf("\tCleared: %lld\n", (long long)tzero); printf("\tReferences: %-18d\n", p->pfik_rulerefs); for (i = 0; i < 8; i++) { af = (i>>2) & 1; diff --git a/sbin/pfctl/tests/files/pf1075.in b/sbin/pfctl/tests/files/pf1075.in new file mode 100644 index 000000000000..835a31a25c6a --- /dev/null +++ b/sbin/pfctl/tests/files/pf1075.in @@ -0,0 +1 @@ +pass inet from (lo0)/24 once diff --git a/sbin/pfctl/tests/files/pf1075.ok b/sbin/pfctl/tests/files/pf1075.ok new file mode 100644 index 000000000000..2369c9410cda --- /dev/null +++ b/sbin/pfctl/tests/files/pf1075.ok @@ -0,0 +1 @@ +pass inet from (lo0)/24 to any flags S/SA keep state once diff --git a/sbin/pfctl/tests/pfctl_test_list.inc b/sbin/pfctl/tests/pfctl_test_list.inc index 8bfccddf50e5..9dd4a590ad8f 100644 --- a/sbin/pfctl/tests/pfctl_test_list.inc +++ b/sbin/pfctl/tests/pfctl_test_list.inc @@ -183,3 +183,4 @@ PFCTL_TEST(1071, "mask length on (lo0)") PFCTL_TEST_FAIL(1072, "Invalid port range") PFCTL_TEST(1073, "Filter AF different than route-to AF, with prefer-ipv6-nexthop") PFCTL_TEST_FAIL(1074, "Filter AF different than route-to AF, without prefer-ipv6-nexthop") +PFCTL_TEST(1075, "One shot rule") |