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")
This commit is contained in:
Kristof Provost
2026-01-12 20:37:08 +01:00
parent 1ee4405a00
commit 8716d8c7d9
13 changed files with 183 additions and 63 deletions
+8 -4
View File
@@ -1313,8 +1313,10 @@ snl_add_msg_attr_pf_rule(struct snl_writer *nw, uint32_t type, const struct pfct
snl_add_msg_attr_ip6(nw, PF_RT_DIVERT_ADDRESS, &r->divert.addr.v6);
snl_add_msg_attr_u16(nw, PF_RT_DIVERT_PORT, r->divert.port);
snl_add_msg_attr_u8(nw, PF_RT_STATE_LIMIT, r->statelim);
snl_add_msg_attr_u8(nw, PF_RT_SOURCE_LIMIT, r->sourcelim);
snl_add_msg_attr_u8(nw, PF_RT_STATE_LIMIT, r->statelim.id);
snl_add_msg_attr_u32(nw, PF_RT_STATE_LIMIT_ACTION, r->statelim.limiter_action);
snl_add_msg_attr_u8(nw, PF_RT_SOURCE_LIMIT, r->sourcelim.id);
snl_add_msg_attr_u32(nw, PF_RT_SOURCE_LIMIT_ACTION, r->sourcelim.limiter_action);
snl_end_attr_nested(nw, off);
}
@@ -1707,8 +1709,10 @@ static struct snl_attr_parser ap_getrule[] = {
{ .type = PF_RT_TYPE_2, .off = _OUT(r.type), .cb = snl_attr_get_uint16 },
{ .type = PF_RT_CODE_2, .off = _OUT(r.code), .cb = snl_attr_get_uint16 },
{ .type = PF_RT_EXPTIME, .off = _OUT(r.exptime), .cb = snl_attr_get_time_t },
{ .type = PF_RT_STATE_LIMIT, .off = _OUT(r.statelim), .cb = snl_attr_get_uint8 },
{ .type = PF_RT_SOURCE_LIMIT, .off = _OUT(r.sourcelim), .cb = snl_attr_get_uint8 },
{ .type = PF_RT_STATE_LIMIT, .off = _OUT(r.statelim.id), .cb = snl_attr_get_uint8 },
{ .type = PF_RT_SOURCE_LIMIT, .off = _OUT(r.sourcelim.id), .cb = snl_attr_get_uint8 },
{ .type = PF_RT_STATE_LIMIT_ACTION, .off = _OUT(r.statelim.limiter_action), .cb = snl_attr_get_uint32 },
{ .type = PF_RT_SOURCE_LIMIT_ACTION, .off = _OUT(r.sourcelim.limiter_action), .cb = snl_attr_get_uint32 },
};
#undef _OUT
SNL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, snl_f_p_empty, ap_getrule);
+8 -2
View File
@@ -249,8 +249,14 @@ struct pfctl_rule {
struct pf_rule_gid gid;
char rcv_ifname[IFNAMSIZ];
bool rcvifnot;
uint8_t statelim;
uint8_t sourcelim;
struct {
uint8_t id;
int limiter_action;
} statelim;
struct {
uint8_t id;
int limiter_action;
} sourcelim;
uint32_t rule_flag;
uint8_t action;
+56 -27
View File
@@ -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;
+8 -4
View File
@@ -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;
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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)
+20 -8
View File
@@ -27,7 +27,7 @@
.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
.Dd December 30, 2025
.Dd January 12, 2026
.Dt PF.CONF 5
.Os
.Sh NAME
@@ -2365,20 +2365,28 @@ For example, the following rule will drop 20% of incoming ICMP packets:
.Bd -literal -offset indent
block in proto icmp probability 20%
.Ed
.It Cm state limiter Ar name
.It Cm state limiter Ar name Oo Cm (limiter options) Oc
Use the specified state limiter to restrict the creation of states
by this rule.
If capacity is not available, the rule does not match and evaluation
of the ruleset continues.
By default if capacity is not available, the rule is ignored
and ruleset evaluation continues with next rule..
Use
.Ic block
option to change default behavior such packet is blocked
when limit is reached.
See the
.Sx State Limiters
section for more information.
.Pp
.It Cm source limiter Ar name
.It Cm source limiter Ar name Oo Cm (limiter options) Oc
Use the specified source limiter to restrict the creation of states
by this rule.
If capacity is not available, the rule does not match and evaluation
of the ruleset continues.
By default if capacity is not available, the rule is ignored
and ruleset evaluation continues with next rule..
Use
.Ic block
option to change default behavior such packet is blocked
when limit is reached.
See the
.Sx Source Limiters
section for more information.
@@ -3614,7 +3622,10 @@ filteropt = user | group | flags | icmp-type | icmp6-type | "tos" tos |
"max-pkt-size" number |
"queue" ( string | "(" string [ [ "," ] string ] ")" ) |
"rtable" number | "probability" number"%" | "prio" number |
"state limiter" name | "source limiter" name |
"state limiter" name |
"state limiter" name "(" limiter-opts ")" |
"source limiter" name |
"source limiter" name "(" limiter-opts ")" | "prio" number |
"dnpipe" ( number | "(" number "," number ")" ) |
"dnqueue" ( number | "(" number "," number ")" ) |
"ridentifier" number |
@@ -3794,6 +3805,7 @@ realtime-sc = "realtime" sc-spec
upperlimit-sc = "upperlimit" sc-spec
sc-spec = ( bandwidth-spec |
"(" bandwidth-spec number bandwidth-spec ")" )
limiter-opts = "block" | "no-match"
include = "include" filename
.Ed
.Sh FILES
+9 -2
View File
@@ -896,8 +896,14 @@ struct pf_krule {
u_int8_t set_prio[2];
sa_family_t naf;
u_int8_t rcvifnot;
uint8_t statelim;
uint8_t sourcelim;
struct {
uint8_t id;
int limiter_action;
} statelim;
struct {
uint8_t id;
int limiter_action;
} sourcelim;
struct {
struct pf_addr addr;
@@ -1433,6 +1439,7 @@ struct pf_test_ctx {
int state_icmp;
int tag;
int rewrite;
int limiter_drop;
u_short reason;
struct pf_src_node *sns[PF_SN_MAX];
struct pf_krule *nr;
+35 -8
View File
@@ -6105,8 +6105,8 @@ pf_match_rule(struct pf_test_ctx *ctx, struct pf_kruleset *ruleset,
pf_osfp_fingerprint(pd, ctx->th),
r->os_fingerprint)),
TAILQ_NEXT(r, entries));
if (r->statelim != PF_STATELIM_ID_NONE) {
stlim = pf_statelim_find(r->statelim);
if (r->statelim.id != PF_STATELIM_ID_NONE) {
stlim = pf_statelim_find(r->statelim.id);
/*
* Treat a missing limiter like an exhausted limiter.
@@ -6123,6 +6123,11 @@ pf_match_rule(struct pf_test_ctx *ctx, struct pf_kruleset *ruleset,
gen = pf_statelim_enter(stlim);
stlim->pfstlim_counters.hardlimited++;
pf_statelim_leave(stlim, gen);
if (r->statelim.limiter_action == PF_LIMITER_BLOCK) {
ctx->limiter_drop = 1;
REASON_SET(&ctx->reason, PFRES_MAXSTATES);
break; /* stop rule processing */
}
r = TAILQ_NEXT(r, entries);
continue;
}
@@ -6140,6 +6145,14 @@ pf_match_rule(struct pf_test_ctx *ctx, struct pf_kruleset *ruleset,
gen = pf_statelim_enter(stlim);
stlim->pfstlim_counters.ratelimited++;
pf_statelim_leave(stlim, gen);
if (r->statelim.limiter_action ==
PF_LIMITER_BLOCK) {
ctx->limiter_drop = 1;
REASON_SET(&ctx->reason,
PFRES_MAXSTATES);
/* stop rule processing */
break;
}
r = TAILQ_NEXT(r, entries);
continue;
}
@@ -6152,10 +6165,10 @@ pf_match_rule(struct pf_test_ctx *ctx, struct pf_kruleset *ruleset,
}
}
if (r->sourcelim != PF_SOURCELIM_ID_NONE) {
if (r->sourcelim.id != PF_SOURCELIM_ID_NONE) {
struct pf_source key;
srlim = pf_sourcelim_find(r->sourcelim);
srlim = pf_sourcelim_find(r->sourcelim.id);
/*
* Treat a missing pool like an overcommitted pool.
@@ -6177,6 +6190,14 @@ pf_match_rule(struct pf_test_ctx *ctx, struct pf_kruleset *ruleset,
gen = pf_sourcelim_enter(srlim);
srlim->pfsrlim_counters.hardlimited++;
pf_sourcelim_leave(srlim, gen);
if (r->sourcelim.limiter_action ==
PF_LIMITER_BLOCK) {
ctx->limiter_drop = 1;
REASON_SET(&ctx->reason,
PFRES_SRCLIMIT);
/* stop rule processing */
break;
}
r = TAILQ_NEXT(r, entries);
continue;
}
@@ -6196,6 +6217,14 @@ pf_match_rule(struct pf_test_ctx *ctx, struct pf_kruleset *ruleset,
srlim->pfsrlim_counters
.ratelimited++;
pf_sourcelim_leave(srlim, gen);
if (r->sourcelim.limiter_action ==
PF_LIMITER_BLOCK) {
ctx->limiter_drop = 1;
REASON_SET(&ctx->reason,
PFRES_SRCLIMIT);
/* stop rules */
break;
}
r = TAILQ_NEXT(r, entries);
continue;
}
@@ -6460,10 +6489,8 @@ pf_test_rule(struct pf_krule **rm, struct pf_kstate **sm,
} else {
ruleset = &pf_main_ruleset;
rv = pf_match_rule(&ctx, ruleset, match_rules);
if (rv == PF_TEST_FAIL) {
/*
* Reason has been set in pf_match_rule() already.
*/
if (rv == PF_TEST_FAIL || ctx.limiter_drop == 1) {
REASON_SET(reason, ctx.reason);
goto cleanup;
}
+5
View File
@@ -501,6 +501,11 @@ struct pf_osfp_ioctl {
#define PF_ANCHOR_HIWAT 512
#define PF_OPTIMIZER_TABLE_PFX "__automatic_"
enum {
PF_LIMITER_NOMATCH,
PF_LIMITER_BLOCK
};
struct pf_rule {
struct pf_rule_addr src;
struct pf_rule_addr dst;
+20
View File
@@ -2922,6 +2922,23 @@ pf_validate_range(uint8_t op, uint16_t port[2])
return 0;
}
static int
pf_chk_limiter_action(int limiter_action)
{
int rv;
switch (limiter_action) {
case PF_LIMITER_NOMATCH:
case PF_LIMITER_BLOCK:
rv = 0;
break;
default:
rv = 1;
}
return (rv);
}
int
pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
uint32_t pool_ticket, const char *anchor, const char *anchor_call,
@@ -2946,6 +2963,9 @@ pf_ioctl_addrule(struct pf_krule *rule, uint32_t ticket,
ERROUT_UNLOCKED(EINVAL);
if (pf_validate_range(rule->dst.port_op, rule->dst.port))
ERROUT_UNLOCKED(EINVAL);
if (pf_chk_limiter_action(rule->statelim.limiter_action) ||
pf_chk_limiter_action(rule->sourcelim.limiter_action))
ERROUT_UNLOCKED(EINVAL);
if (rule->ifname[0])
kif = pf_kkif_create(M_WAITOK);
+8 -4
View File
@@ -784,8 +784,10 @@ static const struct nlattr_parser nla_p_rule[] = {
{ .type = PF_RT_MAX_PKT_SIZE, .off = _OUT(max_pkt_size), .cb = nlattr_get_uint16 },
{ .type = PF_RT_TYPE_2, .off = _OUT(type), .cb = nlattr_get_uint16 },
{ .type = PF_RT_CODE_2, .off = _OUT(code), .cb = nlattr_get_uint16 },
{ .type = PF_RT_STATE_LIMIT, .off = _OUT(statelim), .cb = nlattr_get_uint8 },
{ .type = PF_RT_SOURCE_LIMIT, .off = _OUT(sourcelim), .cb = nlattr_get_uint8 },
{ .type = PF_RT_STATE_LIMIT, .off = _OUT(statelim.id), .cb = nlattr_get_uint8 },
{ .type = PF_RT_SOURCE_LIMIT, .off = _OUT(sourcelim.id), .cb = nlattr_get_uint8 },
{ .type = PF_RT_STATE_LIMIT_ACTION, .off = _OUT(statelim.limiter_action), .cb = nlattr_get_uint32 },
{ .type = PF_RT_SOURCE_LIMIT_ACTION, .off = _OUT(sourcelim.limiter_action), .cb = nlattr_get_uint32 },
};
NL_DECLARE_ATTR_PARSER(rule_parser, nla_p_rule);
#undef _OUT
@@ -1043,8 +1045,10 @@ pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt)
nlattr_add_u64(nw, PF_RT_SRC_NODES_ROUTE, counter_u64_fetch(rule->src_nodes[PF_SN_ROUTE]));
nlattr_add_pf_threshold(nw, PF_RT_PKTRATE, &rule->pktrate);
nlattr_add_time_t(nw, PF_RT_EXPTIME, time_second - (time_uptime - rule->exptime));
nlattr_add_u8(nw, PF_RT_STATE_LIMIT, rule->statelim);
nlattr_add_u8(nw, PF_RT_SOURCE_LIMIT, rule->sourcelim);
nlattr_add_u8(nw, PF_RT_STATE_LIMIT, rule->statelim.id);
nlattr_add_u32(nw, PF_RT_STATE_LIMIT_ACTION, rule->statelim.limiter_action);
nlattr_add_u8(nw, PF_RT_SOURCE_LIMIT, rule->sourcelim.id);
nlattr_add_u32(nw, PF_RT_SOURCE_LIMIT_ACTION, rule->sourcelim.limiter_action);
error = pf_kanchor_copyout(ruleset, rule, anchor_call, sizeof(anchor_call));
MPASS(error == 0);
+4 -2
View File
@@ -299,8 +299,10 @@ enum pf_rule_type_t {
PF_RT_TYPE_2 = 84, /* u16 */
PF_RT_CODE_2 = 85, /* u16 */
PF_RT_EXPTIME = 86, /* time_t */
PF_RT_STATE_LIMIT = 87, /* uint8_t */
PF_RT_SOURCE_LIMIT = 88, /* uint8_t */
PF_RT_STATE_LIMIT = 87, /* u8 */
PF_RT_SOURCE_LIMIT = 88, /* u8 */
PF_RT_STATE_LIMIT_ACTION = 89, /* u32 */
PF_RT_SOURCE_LIMIT_ACTION = 90, /* u32 */
};
enum pf_addrule_type_t {