diff options
| author | Kristof Provost <kp@FreeBSD.org> | 2026-01-12 20:37:08 +0100 |
|---|---|---|
| committer | Kristof Provost <kp@FreeBSD.org> | 2026-01-14 07:44:43 +0100 |
| commit | 8716d8c7d97eec231820ecd1dc50c67beb95d58c (patch) | |
| tree | 2568106cfd450333b70c8803e86ce559837fb695 /sbin | |
| parent | 1ee4405a00d7bcfa5545bba7a78b71cdd4cfdc20 (diff) | |
pf: configurable action on limiter exceeded
This change extends pf(4) limiters so administrator
can specify action the rule executes when limit is
reached. By default when limit is reached the limiter
overrides action specified by rule to no-match.
If administrator wants to block packet instead then
rule with limiter should be changed to:
pass in from any to any state limiter test (block)
OK dlg@
Obtained from: OpenBSD, sashan <sashan@openbsd.org>, 04394254d9
Sponsored by: Rubicon Communications, LLC ("Netgate")
Diffstat (limited to 'sbin')
| -rw-r--r-- | sbin/pfctl/parse.y | 83 | ||||
| -rw-r--r-- | sbin/pfctl/pfctl_parser.c | 12 | ||||
| -rw-r--r-- | sbin/pfctl/tests/files/pf1076.ok | 2 | ||||
| -rw-r--r-- | sbin/pfctl/tests/files/pf1077.ok | 2 |
4 files changed, 66 insertions, 33 deletions
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y index ded74a6391f1..67e0d30890a8 100644 --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -257,6 +257,11 @@ struct redirspec { bool binat; }; +struct limiterspec { + uint32_t id; + int limiter_action; +}; + static struct filter_opts { int marker; #define FOM_FLAGS 0x0001 @@ -287,8 +292,8 @@ static struct filter_opts { u_int32_t tos; u_int32_t prob; u_int32_t ridentifier; - u_int32_t statelim; - u_int32_t sourcelim; + struct limiterspec statelim; + struct limiterspec sourcelim; struct { int action; struct node_state_opt *options; @@ -566,6 +571,7 @@ typedef struct { struct statelim_opts *statelim_opts; struct sourcelim_opts *sourcelim_opts; struct pfctl_watermarks *watermarks; + struct limiterspec limiterspec; } v; int lineno; } YYSTYPE; @@ -600,7 +606,7 @@ int parseport(char *, struct range *r, int); %token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS %token DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON NE LE GE AFTO NATTO RDRTO %token BINATTO MAXPKTRATE MAXPKTSIZE IPV6NH -%token LIMITER ID RATE SOURCE ENTRIES ABOVE BELOW MASK +%token LIMITER ID RATE SOURCE ENTRIES ABOVE BELOW MASK NOMATCH %token <v.string> STRING %token <v.number> NUMBER %token <v.i> PORTBINARY @@ -664,8 +670,8 @@ int parseport(char *, struct range *r, int); %type <v.bridge_to> bridge %type <v.mac> xmac mac mac_list macspec %type <v.string> statelim_nm sourcelim_nm -%type <v.number> statelim_id sourcelim_id -%type <v.number> statelim_filter_opt sourcelim_filter_opt +%type <v.number> statelim_id sourcelim_id limiter_opt limiter_opt_spec +%type <v.limiterspec> statelim_filter_opt sourcelim_filter_opt %type <v.statelim_opts> statelim_opts %type <v.sourcelim_opts> sourcelim_opts %% @@ -2515,20 +2521,22 @@ statelim_opt : statelim_id { ; statelim_filter_opt - : statelim_nm { + : STATE LIMITER STRING limiter_opt_spec { struct pfctl_statelim *stlim; - stlim = pfctl_get_statelim_nm(pf, $1); - free($1); + stlim = pfctl_get_statelim_nm(pf, $3); + free($3); if (stlim == NULL) { yyerror("state limiter not found"); YYERROR; } - $$ = stlim->ioc.id; + $$.id = stlim->ioc.id; + $$.limiter_action = $4; } - | STATE LIMITER statelim_id { - $$ = $3; + | STATE LIMITER statelim_id limiter_opt_spec { + $$.id = $3; + $$.limiter_action = $4; } ; @@ -2760,20 +2768,34 @@ sourcelim_opt_below ; sourcelim_filter_opt - : sourcelim_nm { + : SOURCE LIMITER STRING limiter_opt_spec { struct pfctl_sourcelim *srlim; - srlim = pfctl_get_sourcelim_nm(pf, $1); - free($1); + srlim = pfctl_get_sourcelim_nm(pf, $3); + free($3); if (srlim == NULL) { yyerror("source limiter not found"); YYERROR; } - $$ = srlim->ioc.id; + $$.id = srlim->ioc.id; + $$.limiter_action = $4; } - | SOURCE LIMITER sourcelim_id { - $$ = $3; + | SOURCE LIMITER sourcelim_id limiter_opt_spec { + $$.id = $3; + $$.limiter_action = $4; + } + ; + +limiter_opt_spec: /* empty */ { $$ = PF_LIMITER_NOMATCH; } + | '(' limiter_opt ')' { $$ = $2; } + ; + +limiter_opt: BLOCK { + $$ = PF_LIMITER_BLOCK; + } + | NOMATCH { + $$ = PF_LIMITER_NOMATCH; } ; @@ -3169,16 +3191,20 @@ pfrule : action dir logquick interface route af proto fromto filter_opts : { bzero(&filter_opts, sizeof filter_opts); - filter_opts.statelim = PF_STATELIM_ID_NONE; - filter_opts.sourcelim = PF_SOURCELIM_ID_NONE; + filter_opts.statelim.id = PF_STATELIM_ID_NONE; + filter_opts.statelim.limiter_action = PF_LIMITER_NOMATCH; + filter_opts.sourcelim.id = PF_SOURCELIM_ID_NONE; + filter_opts.sourcelim.limiter_action = PF_LIMITER_NOMATCH; filter_opts.rtableid = -1; } filter_opts_l { $$ = filter_opts; } | /* empty */ { bzero(&filter_opts, sizeof filter_opts); - filter_opts.statelim = PF_STATELIM_ID_NONE; - filter_opts.sourcelim = PF_SOURCELIM_ID_NONE; + filter_opts.statelim.id = PF_STATELIM_ID_NONE; + filter_opts.statelim.limiter_action = PF_LIMITER_NOMATCH; + filter_opts.sourcelim.id = PF_SOURCELIM_ID_NONE; + filter_opts.sourcelim.limiter_action = PF_LIMITER_NOMATCH; filter_opts.rtableid = -1; $$ = filter_opts; } @@ -3323,14 +3349,14 @@ filter_opt : USER uids { filter_opts.prob = 1; } | statelim_filter_opt { - if (filter_opts.statelim != PF_STATELIM_ID_NONE) { + if (filter_opts.statelim.id != PF_STATELIM_ID_NONE) { yyerror("state limiter already specified"); YYERROR; } filter_opts.statelim = $1; } | sourcelim_filter_opt { - if (filter_opts.sourcelim != PF_SOURCELIM_ID_NONE) { + if (filter_opts.sourcelim.id != PF_SOURCELIM_ID_NONE) { yyerror("source limiter already specified"); YYERROR; } @@ -7175,6 +7201,7 @@ lookup(char *s) { "nat-to", NATTO}, { "no", NO}, { "no-df", NODF}, + { "no-match", NOMATCH}, { "no-route", NOROUTE}, { "no-sync", NOSYNC}, { "on", ON}, @@ -8202,11 +8229,11 @@ filteropts_to_rule(struct pfctl_rule *r, struct filter_opts *opts) r->rule_flag |= PFRULE_ONCE; } - if (opts->statelim != PF_STATELIM_ID_NONE && r->action != PF_PASS) { + if (opts->statelim.id != PF_STATELIM_ID_NONE && r->action != PF_PASS) { yyerror("state limiter only applies to pass rules"); return (1); } - if (opts->sourcelim != PF_SOURCELIM_ID_NONE && r->action != PF_PASS) { + if (opts->sourcelim.id != PF_SOURCELIM_ID_NONE && r->action != PF_PASS) { yyerror("source limiter only applies to pass rules"); return (1); } @@ -8215,8 +8242,10 @@ filteropts_to_rule(struct pfctl_rule *r, struct filter_opts *opts) r->pktrate.limit = opts->pktrate.limit; r->pktrate.seconds = opts->pktrate.seconds; r->prob = opts->prob; - r->statelim = opts->statelim; - r->sourcelim = opts->sourcelim; + r->statelim.id = opts->statelim.id; + r->statelim.limiter_action = opts->statelim.limiter_action; + r->sourcelim.id = opts->sourcelim.id; + r->sourcelim.limiter_action = opts->sourcelim.limiter_action; r->rtableid = opts->rtableid; r->ridentifier = opts->ridentifier; r->max_pkt_size = opts->max_pkt_size; diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c index f85c50652944..78a1034a3b43 100644 --- a/sbin/pfctl/pfctl_parser.c +++ b/sbin/pfctl/pfctl_parser.c @@ -1112,7 +1112,7 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int opts, int numeric) } printf(" probability %s%%", buf); } - if (r->statelim != PF_STATELIM_ID_NONE) { + if (r->statelim.id != PF_STATELIM_ID_NONE) { #if 0 /* XXX need pf to find statelims */ struct pfctl_statelim *stlim = pfctl_get_statelim_id(pf, r->statelim); @@ -1121,9 +1121,11 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int opts, int numeric) printf(" state limiter %s", stlim->ioc.name); else #endif - printf(" state limiter id %u", r->statelim); + printf(" state limiter id %u (%s)", r->statelim.id, + (r->statelim.limiter_action == PF_LIMITER_BLOCK) ? + "block" : "no-match"); } - if (r->sourcelim != PF_SOURCELIM_ID_NONE) { + if (r->sourcelim.id != PF_SOURCELIM_ID_NONE) { #if 0 /* XXX need pf to find sourcelims */ struct pfctl_sourcelim *srlim = pfctl_get_sourcelim_id(pf, r->sourcelim); @@ -1132,7 +1134,9 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int opts, int numeric) printf(" source limiter %s", srlim->ioc.name); else #endif - printf(" source limiter id %u", r->sourcelim); + printf(" source limiter id %u (%s)", r->sourcelim.id, + (r->sourcelim.limiter_action == PF_LIMITER_BLOCK) ? + "block" : "no-match"); } ropts = 0; diff --git a/sbin/pfctl/tests/files/pf1076.ok b/sbin/pfctl/tests/files/pf1076.ok index def9533b1e60..9f1a8c8fb5cf 100644 --- a/sbin/pfctl/tests/files/pf1076.ok +++ b/sbin/pfctl/tests/files/pf1076.ok @@ -1,2 +1,2 @@ state limiter dns-server id 1 limit 1000 rate 1/10 -pass in proto tcp from any to any port = domain flags S/SA keep state state limiter id 1 +pass in proto tcp from any to any port = domain flags S/SA keep state state limiter id 1 (no-match) diff --git a/sbin/pfctl/tests/files/pf1077.ok b/sbin/pfctl/tests/files/pf1077.ok index e52afb6bff9c..dc8882e1b87b 100644 --- a/sbin/pfctl/tests/files/pf1077.ok +++ b/sbin/pfctl/tests/files/pf1077.ok @@ -1,2 +1,2 @@ source limiter dns-server id 1 limit 2 states 3 rate 4/5 inet mask 16 -pass in proto tcp from any to any port = domain flags S/SA keep state source limiter id 1 +pass in proto tcp from any to any port = domain flags S/SA keep state source limiter id 1 (no-match) |
