From: Alexandr Nedvedicky Subject: Re: pf(4) add timeout option to ip address tables To: tech@openbsd.org Date: Wed, 24 Jun 2026 23:47:43 +0200 Hello, sorry to keeping you waiting for update. I had no time to get back to it until here at g2k26. updated diff is something what I'd like to get tested. the changes to manual page are yet to be polished. the same goes to code, some comments are still too verbose. this is the sample ruleset I was using to test my changes. 1 table timeout(60) 2 3 pass all 4 5 block in on vio2 6 block return in quick on vio2 from to any 7 pass in on vio2 from 10.188.211.0/24 to any keep state \ (max-src-conn-rate 1/1, overload ) at line 1 table 'minute' with timeout set to 60secs is defined. The table minute is then used by block rule at number 6 and overload action in pass rule at line 7. as soon as max-src-conn-rate 1/1 kicks in the offending IP address is added to overload table. after 60secs timeout elapses the address no longer matches at block rule at line 6. the remote IP address is then free to create yet another state. the expiration timer is reset whenever packet matches the address, this keeps offending IP addresses in table, so the block rule remains in effect. the traffic gets unblocked after offending IP address is silent for desired timeout (60secs in this case). One can use 'pfctl -t minute -T show' to see content of minute table: pf# ./pfctl -t minute -T show 10.188.211.51 ttl 54 output above shows there is on IP address in overload table which is going to expire in 54secs. The output of command above can be redirected to file: pf# ./pfctl -t minute -T show > /tmp/minute.tab If slightly modified ruleset is used: 1 table timeout(60) file "/tmp/minute.tab" 2 3 pass all 4 5 block in on vio2 6 block return in quick on vio2 from to any 7 pass in on vio2 from 10.188.211.0/24 to any keep state \ (max-src-conn-rate 1/1, overload ) then saved addresses can be loaded to table just as shown here: # ./pfctl -t minute -T show 10.188.211.51 ttl 56 # ./pfctl -t minute -T show 10.188.211.51 ttl 48 # ./pfctl -t minute -T show > /tmp/minute.tab # ./pfctl -Fa # tables must be flushed, otherwise restore from /tmp/minute.tab does not happen 1 tables deleted. rules cleared 7 states cleared source tracking entries cleared pf: statistics cleared pf: interface flags reset # ./pfctl -f pf-restore.conf # ./pfctl -t minute -T show 10.188.211.51 ttl 27 note after restoring the table the does not start at 60secs but at the saved value. The expired addresses are not removed immediately. as shown here using verbose output: # ./pfctl -v -t minute -T show 10.188.211.51 Cleared: Wed Jun 24 23:11:26 2026 Expired: 1029s ago The diff below does 'very lazy' removal process. after packet hits expired address the table is flagged to be purged. Then the periodic timer/purge thread steps in and removes all expired entries. So my question now is if people here find it useful, or if there is something what's missing and should be improved before I'll start chasing for OKs. thanks and regards sashan --------8<---------------8<---------------8<------------------8<-------- diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y index 92764edcf3b..afb6dce4eef 100644 --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -348,6 +348,7 @@ struct queue_opts { struct table_opts { int flags; int init_addr; + uint32_t timeout; struct node_tinithead init_nodes; } table_opts; @@ -1401,6 +1402,16 @@ table_opt : STRING { entries); table_opts.init_addr = 1; } + | TIMEOUT '(' NUMBER ')' { + /* + * timeout tables are intended for 'overload' action in + * rules and limiters. They are not supposed to be + * either constant nor manged from command line + * (persistent). Also no support for counters. + */ + table_opts.flags = PFR_TFLAG_TIMEOUT; + table_opts.timeout = $3; + } ; tablespec : xhost optweight { @@ -4508,7 +4519,7 @@ process_tabledef(char *name, struct table_opts *opts, int popts) } if (pf->opts & PF_OPT_VERBOSE) print_tabledef(name, opts->flags, opts->init_addr, - &opts->init_nodes); + opts->timeout, &opts->init_nodes); if (!(pf->opts & PF_OPT_NOACTION) || (pf->opts & PF_OPT_DUMMYACTION)) warn_duplicate_tables(name, pf->anchor->path); @@ -4533,7 +4544,8 @@ process_tabledef(char *name, struct table_opts *opts, int popts) if (!(pf->opts & PF_OPT_NOACTION) && pfctl_define_table(name, opts->flags, opts->init_addr, - pf->anchor->path, &ab, pf->anchor->ruleset.tticket, ukt)) { + pf->anchor->path, opts->timeout, &ab, pf->anchor->ruleset.tticket, + ukt)) { yyerror("cannot define table %s: %s", name, pf_strerror(errno)); goto _error; @@ -5027,7 +5039,7 @@ collapse_redirspec(struct pf_pool *rpool, struct pf_rule *r, if (pf->opts & PF_OPT_VERBOSE) print_tabledef(tbl->pt_name, PFR_TFLAG_CONST | tbl->pt_flags, - 1, &tbl->pt_nodes); + 1, 0, &tbl->pt_nodes); memset(&rpool->addr, 0, sizeof(rpool->addr)); rpool->addr.type = PF_ADDR_TABLE; diff --git a/sbin/pfctl/pfctl.h b/sbin/pfctl/pfctl.h index 2c159b7e5d2..3dd03e03e8c 100644 --- a/sbin/pfctl/pfctl.h +++ b/sbin/pfctl/pfctl.h @@ -39,6 +39,12 @@ #define DBGPRINT(...) (void)(0) #endif +enum { + PFR_ATTR_NONE, + PFR_ATTR_WEIGHT, + PFR_ATTR_TTL +}; + enum pfctl_show { PFCTL_SHOW_RULES, PFCTL_SHOW_LABELS, PFCTL_SHOW_NOTHING }; enum { PFRB_TABLES = 1, PFRB_TSTATS, PFRB_ADDRS, PFRB_ASTATS, diff --git a/sbin/pfctl/pfctl_optimize.c b/sbin/pfctl/pfctl_optimize.c index 81d1bd03d98..7c475754271 100644 --- a/sbin/pfctl/pfctl_optimize.c +++ b/sbin/pfctl/pfctl_optimize.c @@ -564,7 +564,7 @@ combine_rules(struct pfctl *pf, struct superblock *block) if (pf->opts & PF_OPT_VERBOSE) print_tabledef(p1->por_src_tbl->pt_name, - PFR_TFLAG_CONST, 1, + PFR_TFLAG_CONST, 1, 0, &p1->por_src_tbl->pt_nodes); memset(&p1->por_rule.src.addr, 0, @@ -595,7 +595,7 @@ combine_rules(struct pfctl *pf, struct superblock *block) if (pf->opts & PF_OPT_VERBOSE) print_tabledef(p1->por_dst_tbl->pt_name, - PFR_TFLAG_CONST, 1, + PFR_TFLAG_CONST, 1, 0, &p1->por_dst_tbl->pt_nodes); memset(&p1->por_rule.dst.addr, 0, @@ -1289,7 +1289,7 @@ again: tablenum++; if (pfctl_define_table(tbl->pt_name, PFR_TFLAG_CONST | tbl->pt_flags, 1, - pf->astack[0]->path, tbl->pt_buf, pf->astack[0]->ruleset.tticket, + pf->astack[0]->path, 0, tbl->pt_buf, pf->astack[0]->ruleset.tticket, NULL)) { warn("failed to create table %s in %s", tbl->pt_name, pf->astack[0]->name); diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c index f47ee3e8568..0c3037b2df9 100644 --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -1229,7 +1229,7 @@ print_rule(struct pfctl *pf, struct pf_rule *r, const char *anchor_call, } void -print_tabledef(const char *name, int flags, int addrs, +print_tabledef(const char *name, int flags, int addrs, uint32_t timeout, struct node_tinithead *nodes) { struct node_tinit *ti, *nti; @@ -1242,6 +1242,8 @@ print_tabledef(const char *name, int flags, int addrs, printf(" persist"); if (flags & PFR_TFLAG_COUNTERS) printf(" counters"); + if (flags & PFR_TFLAG_TIMEOUT) + printf(" timeout(%u)", timeout); SIMPLEQ_FOREACH(ti, nodes, entries) { if (ti->file) { printf(" file \"%s\"", ti->file); @@ -1894,15 +1896,20 @@ append_addr(struct pfr_buffer *b, char *s, int test, int opts) const char *errstr; int rv, not = 0, i = 0; u_int16_t weight; + u_int32_t ttl; /* skip weight if given */ if (strcmp(s, "weight") == 0) { - expect = 1; + expect = PFR_ATTR_WEIGHT; return (1); /* expecting further call */ + } else if (strcmp(s, "ttl") == 0) { + expect = PFR_ATTR_TTL; + return (1); } /* check if previous host is set */ - if (expect) { + switch (expect) { + case PFR_ATTR_WEIGHT: /* parse and append load balancing weight */ weight = strtonum(s, 1, USHRT_MAX, &errstr); if (errstr) { @@ -1917,8 +1924,24 @@ append_addr(struct pfr_buffer *b, char *s, int test, int opts) } } } - expect = 0; + expect = PFR_ATTR_NONE; + return (0); + case PFR_ATTR_TTL: + ttl = strtonum(s, 1, UINT_MAX, &errstr); + if (errstr) { + fprintf(stderr, "failed to convert ttl %s\n", s); + return (-1); + } + if (previous != -1) { + PFRB_FOREACH(a, b) { + if (++i >= previous) + a->pfra_expire = ttl; + } + } + expect = PFR_ATTR_NONE; return (0); + default: + (void)(0); } for (r = s; *r == '!'; r++) diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h index c65a805ad90..475e37ced8e 100644 --- a/sbin/pfctl/pfctl_parser.h +++ b/sbin/pfctl/pfctl_parser.h @@ -53,6 +53,7 @@ #define PF_OPT_PORTNAMES 0x08000 #define PF_OPT_IGNFAIL 0x10000 #define PF_OPT_CALLSHOW 0x20000 +#define PF_OPT_NOTTL 0x40000 #define PF_TH_ALL 0xFF @@ -276,17 +277,19 @@ int pfctl_load_queues(struct pfctl *); int pfctl_add_queue(struct pfctl *, struct pf_queuespec *); struct pfctl_qsitem * pfctl_find_queue(char *, struct pf_qihead *); -void print_pool(struct pf_pool *, u_int16_t, u_int16_t, sa_family_t, int, int); +void print_pool(struct pf_pool *, u_int16_t, u_int16_t, sa_family_t, int, + int); void print_src_node(struct pf_src_node *, int); void print_statelim(const struct pfioc_statelim *); void print_sourcelim(const struct pfioc_sourcelim *); void print_rule(struct pfctl *pf, struct pf_rule *, const char *, int); -void print_tabledef(const char *, int, int, struct node_tinithead *); +void print_tabledef(const char *, int, int, uint32_t, + struct node_tinithead *); void print_status(struct pf_status *, struct pfctl_watermarks *, int); void print_queuespec(struct pf_queuespec *); -int pfctl_define_table(char *, int, int, const char *, struct pfr_buffer *, - u_int32_t, struct pfr_uktable *); +int pfctl_define_table(char *, int, int, const char *, u_int32_t, + struct pfr_buffer *, u_int32_t, struct pfr_uktable *); void pfctl_expand_label_nr(struct pf_rule *, unsigned int); void pfctl_clear_fingerprints(int, int); diff --git a/sbin/pfctl/pfctl_table.c b/sbin/pfctl/pfctl_table.c index e460adf5f23..65b8a1f1a0a 100644 --- a/sbin/pfctl/pfctl_table.c +++ b/sbin/pfctl/pfctl_table.c @@ -207,8 +207,7 @@ pfctl_table(int argc, char *argv[], char *tname, const char *command, PFRB_FOREACH(a, &b) if (opts & PF_OPT_VERBOSE2 || a->pfra_fback != PFR_FB_NONE) - print_addrx(a, NULL, - opts & PF_OPT_USEDNS); + print_addrx(a, NULL, opts); } else if (!strcmp(command, "delete")) { b.pfrb_type = PFRB_ADDRS; if (load_addr(&b, argc, argv, file, 0, opts)) @@ -222,8 +221,7 @@ pfctl_table(int argc, char *argv[], char *tname, const char *command, PFRB_FOREACH(a, &b) if (opts & PF_OPT_VERBOSE2 || a->pfra_fback != PFR_FB_NONE) - print_addrx(a, NULL, - opts & PF_OPT_USEDNS); + print_addrx(a, NULL, opts); } else if (!strcmp(command, "replace")) { b.pfrb_type = PFRB_ADDRS; if (load_addr(&b, argc, argv, file, 0, opts)) @@ -254,8 +252,7 @@ pfctl_table(int argc, char *argv[], char *tname, const char *command, PFRB_FOREACH(a, &b) if (opts & PF_OPT_VERBOSE2 || a->pfra_fback != PFR_FB_NONE) - print_addrx(a, NULL, - opts & PF_OPT_USEDNS); + print_addrx(a, NULL, opts); } else if (!strcmp(command, "expire")) { const char *errstr; u_int lifetime; @@ -293,8 +290,7 @@ pfctl_table(int argc, char *argv[], char *tname, const char *command, PFRB_FOREACH(a, &b2) if (opts & PF_OPT_VERBOSE2 || a->pfra_fback != PFR_FB_NONE) - print_addrx(a, NULL, - opts & PF_OPT_USEDNS); + print_addrx(a, NULL, opts); } else if (!strcmp(command, "show")) { b.pfrb_type = (opts & PF_OPT_VERBOSE) ? PFRB_ASTATS : PFRB_ADDRS; @@ -314,9 +310,9 @@ pfctl_table(int argc, char *argv[], char *tname, const char *command, } PFRB_FOREACH(p, &b) if (opts & PF_OPT_VERBOSE) - print_astats(p, opts & PF_OPT_USEDNS); + print_astats(p, opts); else - print_addrx(p, NULL, opts & PF_OPT_USEDNS); + print_addrx(p, NULL, opts); } else if (!strcmp(command, "test")) { b.pfrb_type = PFRB_ADDRS; b2.pfrb_type = PFRB_ADDRS; @@ -335,13 +331,12 @@ pfctl_table(int argc, char *argv[], char *tname, const char *command, if ((opts & PF_OPT_VERBOSE) && !(opts & PF_OPT_VERBOSE2)) PFRB_FOREACH(a, &b) if (a->pfra_fback == PFR_FB_MATCH) - print_addrx(a, NULL, - opts & PF_OPT_USEDNS); + print_addrx(a, NULL, opts); if (opts & PF_OPT_VERBOSE2) { a2 = NULL; PFRB_FOREACH(a, &b) { a2 = pfr_buf_next(&b2, a2); - print_addrx(a2, a, opts & PF_OPT_USEDNS); + print_addrx(a2, a, opts); } } if (nmatch < b.pfrb_size) @@ -359,8 +354,7 @@ pfctl_table(int argc, char *argv[], char *tname, const char *command, PFRB_FOREACH(a, &b) if (opts & PF_OPT_VERBOSE2 || a->pfra_fback != PFR_FB_NONE) - print_addrx(a, NULL, - opts & PF_OPT_USEDNS); + print_addrx(a, NULL, opts); } else if (!strcmp(command, "zero")) { flags |= PFR_FLAG_ADDRSTOO; RVTEST(pfr_clr_tstats(&table, 1, &nzero, flags)); @@ -383,18 +377,21 @@ print_table(struct pfr_table *ta, int verbose, int debug) if (!debug && !(ta->pfrt_flags & PFR_TFLAG_ACTIVE)) return; if (verbose) - printf("%c%c%c%c%c%c%c\t", + printf("%c%c%c%c%c%c%c%c\t", (ta->pfrt_flags & PFR_TFLAG_CONST) ? 'c' : '-', (ta->pfrt_flags & PFR_TFLAG_PERSIST) ? 'p' : '-', (ta->pfrt_flags & PFR_TFLAG_ACTIVE) ? 'a' : '-', (ta->pfrt_flags & PFR_TFLAG_INACTIVE) ? 'i' : '-', (ta->pfrt_flags & PFR_TFLAG_REFERENCED) ? 'r' : '-', (ta->pfrt_flags & PFR_TFLAG_REFDANCHOR) ? 'h' : '-', + (ta->pfrt_flags & PFR_TFLAG_TIMEOUT) ? 't' : '-', (ta->pfrt_flags & PFR_TFLAG_COUNTERS) ? 'C' : '-'); printf("%s", ta->pfrt_name); if (ta->pfrt_anchor[0] != '\0') printf("@%s", ta->pfrt_anchor); + if (verbose && ta->pfrt_flags & PFR_TFLAG_TIMEOUT) + printf(" timeout(%u)", ta->pfrt_timeout); printf("\n"); } @@ -452,11 +449,21 @@ load_addr(struct pfr_buffer *b, int argc, char *argv[], char *file, } void -print_addrx(struct pfr_addr *ad, struct pfr_addr *rad, int dns) +print_addrx(struct pfr_addr *ad, struct pfr_addr *rad, int opts) { char ch, buf[256] = "{error}"; char fb[] = { ' ', 'M', 'A', 'D', 'C', 'Z', 'X', ' ', 'Y', ' ' }; unsigned int fback, hostnet; + int dns = (opts & PF_OPT_USEDNS); + int nottl = (opts & PF_OPT_NOTTL); + time_t now = time(NULL); + + /* + * do not print expired addresses unless caller asks to + * ignore expiration timer. + */ + if (!nottl && ad->pfra_expire && ad->pfra_expire < now) + return; fback = (rad != NULL) ? rad->pfra_fback : ad->pfra_fback; ch = (fback < sizeof(fb)/sizeof(*fb)) ? fb[fback] : '?'; @@ -499,22 +506,44 @@ print_addrx(struct pfr_addr *ad, struct pfr_addr *rad, int dns) } if (ad->pfra_ifname[0] != '\0') printf("@%s", ad->pfra_ifname); + + /* + * suppress printing of TTL when caller wants expiration + * timer to be ignored. + */ + if (!nottl && ad->pfra_expire && ad->pfra_expire > now) + printf("\tttl\t%llu", ad->pfra_expire - now); + printf("\n"); } void -print_astats(struct pfr_astats *as, int dns) +print_astats(struct pfr_astats *as, int opts) { - time_t time = as->pfras_tzero; + time_t tzero = as->pfras_tzero; int dir, op; char *ct; + time_t now = time(NULL); - ct = ctime(&time); - print_addrx(&as->pfras_a, NULL, dns); + ct = ctime(&tzero); + /* + * suppress printing of TTL for addresses with expiration + * timer set. The TTL is printed here in verbose fashion. + */ + print_addrx(&as->pfras_a, NULL, opts | PF_OPT_NOTTL); if (ct) - printf("\tCleared: %s", ctime(&time)); + printf("\tCleared: %s", ct); else - printf("\tCleared: %lld\n", time); + printf("\tCleared: %lld\n", tzero); + + if (as->pfras_a.pfra_expire) { + if (as->pfras_a.pfra_expire < now) + printf("\tExpired: %llds ago\n", + now - as->pfras_a.pfra_expire); + else + printf("\tExpires in: %llds\n", + as->pfras_a.pfra_expire - now); + } if (as->pfras_a.pfra_states) printf("\tActive States: %d\n", as->pfras_a.pfra_states); if (as->pfras_a.pfra_type == PFRKE_COST) @@ -533,7 +562,8 @@ 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_uktable *ukt) + u_int32_t timeout, struct pfr_buffer *ab, u_int32_t ticket, + struct pfr_uktable *ukt) { struct pfr_table tbl_buf; struct pfr_table *tbl; @@ -563,8 +593,9 @@ pfctl_define_table(char *name, int flags, int addrs, const char *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); + tbl->pfrt_timeout = timeout; + DBGPRINT("%s %s@%s [%x] (%u)\n", __func__, tbl->pfrt_name, + tbl->pfrt_anchor, tbl->pfrt_flags, tbl->pfrt_timeout); /* * non-root anchors processed by parse.y are loaded to kernel later. diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5 index 3a383b23517..59ece54039b 100644 --- a/share/man/man5/pf.conf.5 +++ b/share/man/man5/pf.conf.5 @@ -1822,6 +1822,15 @@ The flag forces the kernel to keep the table even when no rules refer to it. If the flag is not set, the kernel will automatically remove the table when the last rule referring to it is flushed. +.It timeout +The +.Cm timeout +option sets timeout (in seconds) for which the IP address is kept in table. +This is meant for tables used by +.Cm overload +action either in rule or in limiter. +After the timeout elapses the IP address is removed from the table where +timeout is set. .El .Pp This example creates a table called @@ -3021,7 +3030,8 @@ antispoof-rule = "antispoof" [ "log" ] [ "quick" ] table-rule = "table" "<" string ">" [ tableopts ] tableopts = tableopt [ tableopts ] tableopt = "persist" | "const" | "counters" | - "file" string | "{" [ tableaddrs ] "}" + "timeout" "(" number ")" | "file" string | + "{" [ tableaddrs ] "}" tableaddrs = tableaddr-spec [ [ "," ] tableaddrs ] tableaddr-spec = [ "!" ] tableaddr [ "/" mask-bits ] tableaddr = hostname | ifspec | "self" | diff --git a/sys/net/pf.c b/sys/net/pf.c index 3ec43778805..702bd278ef3 100644 --- a/sys/net/pf.c +++ b/sys/net/pf.c @@ -1967,6 +1967,7 @@ pf_purge(void *null) pf_purge_expired_src_nodes(); pf_source_purge(); + pfr_purge_overload(); PF_UNLOCK(); diff --git a/sys/net/pf_table.c b/sys/net/pf_table.c index 45c5533e55b..2da3a7c46c3 100644 --- a/sys/net/pf_table.c +++ b/sys/net/pf_table.c @@ -118,7 +118,8 @@ struct pfr_walktree { PFRW_GET_ADDRS, PFRW_GET_ASTATS, PFRW_POOL_GET, - PFRW_DYNADDR_UPDATE + PFRW_DYNADDR_UPDATE, + PFRW_ENQUEUE_EXPIRED } pfrw_op; union { struct pfr_addr *pfrw1_addr; @@ -129,6 +130,7 @@ struct pfr_walktree { } pfrw_1; int pfrw_free; int pfrw_flags; + time_t pfrw_now; }; #define pfrw_addr pfrw_1.pfrw1_addr #define pfrw_astats pfrw_1.pfrw1_astats @@ -959,6 +961,7 @@ pfr_create_kentry(struct pfr_addr *ad) switch (ke->pfrke_type) { case PFRKE_PLAIN: + ((struct pfr_kentry *)ke)->pfrke_expire = ad->pfra_expire; break; case PFRKE_COST: ((struct pfr_kentry_cost *)ke)->weight = ad->pfra_weight; @@ -1016,6 +1019,8 @@ pfr_create_kentry_unlocked(struct pfr_addr *ad, int flags) switch (ke->pfrke_type) { case PFRKE_PLAIN: + ((struct pfr_kentry_cost *)ke)->pfrke_expire = ad->pfra_expire; + break; break; case PFRKE_COST: ((struct pfr_kentry_cost *)ke)->weight = ad->pfra_weight; @@ -1121,6 +1126,8 @@ pfr_insert_kentries(struct pfr_ktable *kt, break; } p->pfrke_tzero = tzero; + if (kt->pfrkt_flags & PFR_TFLAG_TIMEOUT && p->pfrke_expire) + p->pfrke_expire += tzero; ++n; if (p->pfrke_type == PFRKE_COST) kt->pfrkt_refcntcost++; @@ -1137,8 +1144,15 @@ pfr_insert_kentry(struct pfr_ktable *kt, struct pfr_addr *ad, time_t tzero) int rv; p = pfr_lookup_addr(kt, ad, 1); - if (p != NULL) + if (p != NULL) { + /* + * we typically arrive here on behalf of overload action, + * just reset timer to keep offending IP address in table. + */ + if (kt->pfrkt_flags & PFR_TFLAG_TIMEOUT) + p->pfrke_expire = tzero + kt->pfrkt_timeout; return (0); + } p = pfr_create_kentry(ad); if (p == NULL) return (EINVAL); @@ -1148,6 +1162,8 @@ pfr_insert_kentry(struct pfr_ktable *kt, struct pfr_addr *ad, time_t tzero) return (rv); p->pfrke_tzero = tzero; + if (kt->pfrkt_flags & PFR_TFLAG_TIMEOUT) + p->pfrke_expire = tzero + kt->pfrkt_timeout; if (p->pfrke_type == PFRKE_COST) kt->pfrkt_refcntcost++; kt->pfrkt_cnt++; @@ -1353,6 +1369,7 @@ pfr_copyout_addr(struct pfr_addr *ad, struct pfr_kentry *ke) ad->pfra_af = ke->pfrke_af; ad->pfra_net = ke->pfrke_net; ad->pfra_type = ke->pfrke_type; + ad->pfra_expire = ke->pfrke_expire; if (ke->pfrke_flags & PFRKE_FLAG_NOT) ad->pfra_not = 1; @@ -1473,6 +1490,12 @@ pfr_walktree(struct radix_node *rn, void *arg, u_int id) unhandled_af(ke->pfrke_af); } break; + case PFRW_ENQUEUE_EXPIRED: + if (ke->pfrke_expire != 0 && ke->pfrke_expire < w->pfrw_now) { + SLIST_INSERT_HEAD(w->pfrw_workq, ke, pfrke_workq); + w->pfrw_cnt++; + } + break; } return (0); } @@ -2253,6 +2276,28 @@ void pfr_setflags_ktable(struct pfr_ktable *kt, int newf) { struct pfr_kentryworkq addrq; + int keep_timeout = kt->pfrkt_flags & PFR_TFLAG_TIMEOUT; + + /* + * the keep_timeout flag is poor man's solution to table flags + * limitations. PFR_TFLAG_TIMEOUT is user defined flag and as + * such must be covered by PFR_TFLAG_USRMASK so the table can + * pass validation in pfr_validate_table() when it arrives via + * ioctl(2). + * + * when pfr_ina_define() creates table on behalf of pf.conf parser, + * the table is marked as inactive (PFR_TFLAG_ACTIVE flag is not set). + * Here all inactive tables loose all user defined flags when this + * line is executed for inactive table: + * newf &= ~PFR_TFLAG_USRMASK; + * kt->pfrkt_flags: 0x88 newf: 0x98 + * + * However the PFR_TFLAG_TIMEOUT must be kept around for + * pfr_insert_kentries() which is executed later when IP addresses + * are being loaded to table. The _TIMEOUT flag tells + * pfr_insert_kentries() to arm expiration timer for addresses that + * are loaded. + */ if (!(newf & PFR_TFLAG_REFERENCED) && !(newf & PFR_TFLAG_REFDANCHOR) && @@ -2279,7 +2324,7 @@ pfr_setflags_ktable(struct pfr_ktable *kt, int newf) pfr_destroy_ktable(kt->pfrkt_shadow, 1); kt->pfrkt_shadow = NULL; } - kt->pfrkt_flags = newf; + kt->pfrkt_flags = newf | keep_timeout; } void @@ -2421,6 +2466,25 @@ pfr_lookup_table(struct pfr_table *tbl) (struct pfr_ktable *)tbl)); } +int +pfr_kentry_expired(struct pfr_ktable *kt, struct pfr_kentry *ke) +{ + time_t now; + int expired; + + if (kt->pfrkt_flags & PFR_TFLAG_TIMEOUT && ke->pfrke_expire != 0) { + now = gettime(); + expired = (ke->pfrke_expire < now); + /* + * reset timer to keep offending IP address in. + */ + ke->pfrke_expire = now + kt->pfrkt_timeout; + } else + expired = 0; + + return (expired); +} + int pfr_match_addr(struct pfr_ktable *kt, struct pf_addr *a, sa_family_t af) { @@ -2430,10 +2494,15 @@ pfr_match_addr(struct pfr_ktable *kt, struct pf_addr *a, sa_family_t af) ke = pfr_kentry_byaddr(kt, a, af, 0); match = (ke && !(ke->pfrke_flags & PFRKE_FLAG_NOT)); - if (match) + if (match && pfr_kentry_expired(kt, ke) == 0) kt->pfrkt_match++; - else + else { kt->pfrkt_nomatch++; + if (ke != NULL) { + match = 0; + kt->pfrkt_flags |= PFR_TFLAG_NEED_PURGE; + } + } return (match); } @@ -2913,3 +2982,25 @@ pfr_ktable_select_active(struct pfr_ktable *kt) return (kt); } + +void +pfr_purge_overload(void) +{ + struct pfr_ktable *kt; + struct pfr_kentryworkq workq; + struct pfr_walktree w; + + RB_FOREACH(kt, pfr_ktablehead, &pfr_ktables) { + if (kt->pfrkt_flags & PFR_TFLAG_NEED_PURGE) { + bzero(&w, sizeof(w)); + w.pfrw_op = PFRW_ENQUEUE_EXPIRED; + w.pfrw_now = gettime(); + w.pfrw_workq = &workq; + SLIST_INIT(&workq); + rn_walktree(kt->pfrkt_ip4, pfr_walktree, &w); + rn_walktree(kt->pfrkt_ip6, pfr_walktree, &w); + pfr_remove_kentries(kt, &workq); + kt->pfrkt_flags &= ~PFR_TFLAG_NEED_PURGE; + } + } +} diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index 750fe0ef144..5cc0bb8c3c9 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -872,15 +872,18 @@ RB_PROTOTYPE(pf_anchor_node, pf_anchor, entry_node, pf_anchor_compare) #define PFR_TFLAG_REFERENCED 0x00000010 #define PFR_TFLAG_REFDANCHOR 0x00000020 #define PFR_TFLAG_COUNTERS 0x00000040 +#define PFR_TFLAG_TIMEOUT 0x00000080 +#define PFR_TFLAG_NEED_PURGE 0x00000100 /* Adjust masks below when adding flags. */ -#define PFR_TFLAG_USRMASK 0x00000043 -#define PFR_TFLAG_SETMASK 0x0000003C -#define PFR_TFLAG_ALLMASK 0x0000007F +#define PFR_TFLAG_USRMASK 0x000000C3 +#define PFR_TFLAG_SETMASK 0x0000013C +#define PFR_TFLAG_ALLMASK 0x000001FF struct pfr_table { char pfrt_anchor[PATH_MAX]; char pfrt_name[PF_TABLE_NAME_SIZE]; u_int32_t pfrt_flags; + u_int32_t pfrt_timeout; u_int8_t pfrt_fback; }; @@ -894,6 +897,7 @@ struct pfr_addr { struct in6_addr _pfra_ip6addr; } pfra_u; char pfra_ifname[IFNAMSIZ]; + time_t pfra_expire; u_int32_t pfra_states; u_int16_t pfra_weight; u_int8_t pfra_af; @@ -932,6 +936,7 @@ struct pfr_tstats { }; #define pfrts_name pfrts_t.pfrt_name #define pfrts_flags pfrts_t.pfrt_flags +#define pfrts_timeout pfrts_t.pfrt_timeout struct pfr_kcounters { u_int64_t pfrkc_packets[PFR_DIR_MAX][PFR_OP_ADDR_MAX]; @@ -958,6 +963,7 @@ struct _pfr_kentry { SLIST_ENTRY(pfr_kentry) _pfrke_ioq; struct pfr_kcounters *_pfrke_counters; time_t _pfrke_tzero; + time_t _pfrke_expire; u_int8_t _pfrke_af; u_int8_t _pfrke_net; u_int8_t _pfrke_flags; @@ -981,6 +987,7 @@ struct pfr_kentry { #define pfrke_ioq u._ke._pfrke_ioq #define pfrke_counters u._ke._pfrke_counters #define pfrke_tzero u._ke._pfrke_tzero +#define pfrke_expire u._ke._pfrke_expire #define pfrke_af u._ke._pfrke_af #define pfrke_net u._ke._pfrke_net #define pfrke_flags u._ke._pfrke_flags @@ -1047,6 +1054,7 @@ struct pfr_ktable { #define pfrkt_match pfrkt_ts.pfrts_match #define pfrkt_nomatch pfrkt_ts.pfrts_nomatch #define pfrkt_tzero pfrkt_ts.pfrts_tzero +#define pfrkt_timeout pfrkt_ts.pfrts_timeout RB_HEAD(pfi_ifhead, pfi_kif); @@ -1895,6 +1903,7 @@ int pfr_ina_define(struct pfr_table *, struct pfr_addr *, int, int *, int *, u_int32_t, int); struct pfr_ktable *pfr_ktable_select_active(struct pfr_ktable *); +void pfr_purge_overload(void); extern struct pfi_kif *pfi_all;