Page MenuHomeFreeBSD

D50798.id158743.diff
No OneTemporary

D50798.id158743.diff

diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -159,6 +159,13 @@
uint32_t ticket;
};
+struct pfctl_threshold {
+ uint32_t limit;
+ uint32_t seconds;
+ uint32_t count;
+ uint32_t last;
+};
+
struct pfctl_rule {
struct pf_rule_addr src;
struct pf_rule_addr dst;
@@ -181,6 +188,7 @@
struct pfctl_pool rdr;
};
struct pfctl_pool route;
+ struct pfctl_threshold pktrate;
uint64_t evaluations;
uint64_t packets[2];
@@ -396,13 +404,6 @@
uint32_t halfopen_states;
};
-struct pfctl_threshold {
- uint32_t limit;
- uint32_t seconds;
- uint32_t count;
- uint32_t last;
-};
-
struct pfctl_src_node {
struct pf_addr addr;
struct pf_addr raddr;
diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -1208,6 +1208,19 @@
snl_end_attr_nested(nw, off);
}
+static void
+snl_add_msg_attr_threshold(struct snl_writer *nw, uint32_t type, const struct pfctl_threshold *th)
+{
+ int off;
+
+ off = snl_add_msg_attr_nested(nw, type);
+
+ snl_add_msg_attr_u32(nw, PF_TH_LIMIT, th->limit);
+ snl_add_msg_attr_u32(nw, PF_TH_SECONDS, th->seconds);
+
+ snl_end_attr_nested(nw, off);
+}
+
static void
snl_add_msg_attr_pf_rule(struct snl_writer *nw, uint32_t type, const struct pfctl_rule *r)
{
@@ -1228,6 +1241,7 @@
snl_add_msg_attr_rpool(nw, PF_RT_RPOOL_RDR, &r->rdr);
snl_add_msg_attr_rpool(nw, PF_RT_RPOOL_NAT, &r->nat);
snl_add_msg_attr_rpool(nw, PF_RT_RPOOL_RT, &r->route);
+ snl_add_msg_attr_threshold(nw, PF_RT_PKTRATE, &r->pktrate);
snl_add_msg_attr_u32(nw, PF_RT_OS_FINGERPRINT, r->os_fingerprint);
snl_add_msg_attr_u32(nw, PF_RT_RTABLEID, r->rtableid);
snl_add_msg_attr_timeouts(nw, PF_RT_TIMEOUT, r->timeout);
@@ -1581,6 +1595,15 @@
SNL_DECLARE_ATTR_PARSER(rule_uid_parser, ap_rule_uid);
#undef _OUT
+#define _OUT(_field) offsetof(struct pfctl_threshold, _field)
+static const struct snl_attr_parser ap_pfctl_threshold[] = {
+ { .type = PF_TH_LIMIT, .off = _OUT(limit), .cb = snl_attr_get_uint32 },
+ { .type = PF_TH_SECONDS, .off = _OUT(seconds), .cb = snl_attr_get_uint32 },
+ { .type = PF_TH_COUNT, .off = _OUT(count), .cb = snl_attr_get_uint32 },
+};
+SNL_DECLARE_ATTR_PARSER(pfctl_threshold_parser, ap_pfctl_threshold);
+#undef _OUT
+
struct pfctl_nl_get_rule {
struct pfctl_rule r;
char anchor_call[MAXPATHLEN];
@@ -1668,6 +1691,7 @@
{ .type = PF_RT_SRC_NODES_LIMIT, .off = _OUT(r.src_nodes_type[PF_SN_LIMIT]), .cb = snl_attr_get_uint64 },
{ .type = PF_RT_SRC_NODES_NAT, .off = _OUT(r.src_nodes_type[PF_SN_NAT]), .cb = snl_attr_get_uint64 },
{ .type = PF_RT_SRC_NODES_ROUTE, .off = _OUT(r.src_nodes_type[PF_SN_ROUTE]), .cb = snl_attr_get_uint64 },
+ { .type = PF_RT_PKTRATE, .off = _OUT(r.pktrate), .arg = &pfctl_threshold_parser, .cb = snl_attr_get_nested },
};
#undef _OUT
SNL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, snl_f_p_empty, ap_getrule);
@@ -3001,16 +3025,6 @@
return (e.error);
}
-#define _OUT(_field) offsetof(struct pfctl_threshold, _field)
-static const struct snl_attr_parser ap_pfctl_threshold[] = {
- { .type = PF_TH_LIMIT, .off = _OUT(limit), .cb = snl_attr_get_uint32 },
- { .type = PF_TH_SECONDS, .off = _OUT(seconds), .cb = snl_attr_get_uint32 },
- { .type = PF_TH_COUNT, .off = _OUT(count), .cb = snl_attr_get_uint32 },
- { .type = PF_TH_LAST, .off = _OUT(last), .cb = snl_attr_get_uint32 },
-};
-SNL_DECLARE_ATTR_PARSER(pfctl_threshold_parser, ap_pfctl_threshold);
-#undef _OUT
-
#define _OUT(_field) offsetof(struct pfctl_src_node, _field)
static struct snl_attr_parser ap_srcnode[] = {
{ .type = PF_SN_ADDR, .off = _OUT(addr), .cb = snl_attr_get_in6_addr },
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -308,6 +308,10 @@
int settos;
int randomid;
int max_mss;
+ struct {
+ uint32_t limit;
+ uint32_t seconds;
+ } pktrate;
} filter_opts;
static struct antispoof_opts {
@@ -531,7 +535,7 @@
%token MAXSRCCONN MAXSRCCONNRATE OVERLOAD FLUSH SLOPPY PFLOW ALLOW_RELATED
%token TAGGED TAG IFBOUND FLOATING STATEPOLICY STATEDEFAULTS ROUTE SETTOS
%token DIVERTTO DIVERTREPLY BRIDGE_TO RECEIVEDON NE LE GE AFTO NATTO RDRTO
-%token BINATTO
+%token BINATTO MAXPKTRATE
%token <v.string> STRING
%token <v.number> NUMBER
%token <v.i> PORTBINARY
@@ -1012,6 +1016,8 @@
r.prob = $9.prob;
r.rtableid = $9.rtableid;
r.ridentifier = $9.ridentifier;
+ r.pktrate.limit = $9.pktrate.limit;
+ r.pktrate.seconds = $9.pktrate.seconds;
if ($9.tag)
if (strlcpy(r.tagname, $9.tag,
@@ -2489,6 +2495,8 @@
r.tos = $9.tos;
r.keep_state = $9.keep.action;
+ r.pktrate.limit = $9.pktrate.limit;
+ r.pktrate.seconds = $9.pktrate.seconds;
o = $9.keep.options;
/* 'keep state' by default on pass rules. */
@@ -3112,6 +3120,19 @@
}
filter_opts.marker |= FOM_AFTO;
}
+ | MAXPKTRATE NUMBER '/' NUMBER {
+ if ($2 < 0 || $2 > UINT_MAX ||
+ $4 < 0 || $4 > UINT_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ if (filter_opts.pktrate.limit) {
+ yyerror("cannot respecify max-pkt-rate");
+ YYERROR;
+ }
+ filter_opts.pktrate.limit = $2;
+ filter_opts.pktrate.seconds = $4;
+ }
| filter_sets
;
@@ -6697,6 +6718,7 @@
{ "matches", MATCHES},
{ "max", MAXIMUM},
{ "max-mss", MAXMSS},
+ { "max-pkt-rate", MAXPKTRATE},
{ "max-src-conn", MAXSRCCONN},
{ "max-src-conn-rate", MAXSRCCONNRATE},
{ "max-src-nodes", MAXSRCNODES},
diff --git a/sbin/pfctl/pfctl_parser.c b/sbin/pfctl/pfctl_parser.c
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -1007,6 +1007,9 @@
printf(" tos 0x%2.2x", r->tos);
if (r->prio)
printf(" prio %u", r->prio == PF_PRIO_ZERO ? 0 : r->prio);
+ if (r->pktrate.limit)
+ printf(" max-pkt-rate %u/%u", r->pktrate.limit,
+ r->pktrate.seconds);
if (r->scrub_flags & PFSTATE_SETMASK) {
char *comma = "";
printf(" set (");
diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5
--- a/share/man/man5/pf.conf.5
+++ b/share/man/man5/pf.conf.5
@@ -27,7 +27,7 @@
.\" ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
.\" POSSIBILITY OF SUCH DAMAGE.
.\"
-.Dd June 12, 2025
+.Dd June 17, 2025
.Dt PF.CONF 5
.Os
.Sh NAME
@@ -2216,6 +2216,22 @@
.It Ar ridentifier Aq Ar number
Add an identifier (number) to the rule, which can be used to correlate the rule
to pflog entries, even after ruleset updates.
+.It Cm max-pkt-rate Ar number Ns / Ns Ar seconds
+Measure the rate of packets matching the rule and states created by it.
+When the specified rate is exceeded, the rule stops matching.
+Only packets in the direction in which the state was created are considered,
+so that typically requests are counted and replies are not.
+For example:
+.Pp
+.Bd -literal -offset indent -compact
+block in proto icmp
+pass in proto icmp max-pkt-rate 100/10
+.Ed
+.Pp
+passes up to 100 icmp packets per 10 seconds.
+When the rate is exceeded, all icmp is blocked until the rate falls below
+100 per 10 seconds again.
+.Pp
.It Xo Ar queue Aq Ar queue
.No \*(Ba ( Aq Ar queue ,
.Aq Ar queue )
@@ -3388,6 +3404,7 @@
"max-mss" number | "random-id" | "reassemble tcp" |
fragmentation | "allow-opts" |
"label" string | "tag" string | [ "!" ] "tagged" string |
+ "max-pkt-rate" number "/" seconds |
"set prio" ( number | "(" number [ [ "," ] number ] ")" ) |
"queue" ( string | "(" string [ [ "," ] string ] ")" ) |
"rtable" number | "probability" number"%" | "prio" number |
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -821,6 +821,7 @@
struct pf_kpool nat;
struct pf_kpool rdr;
struct pf_kpool route;
+ struct pf_kthreshold pktrate;
struct pf_counter_u64 evaluations;
struct pf_counter_u64 packets[2];
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -445,6 +445,12 @@
SDT_PROBE5(pf, ip, state, lookup, pd->kif, k, (pd->dir), pd, (s)); \
if ((s) == NULL) \
return (PF_DROP); \
+ if ((s)->rule->pktrate.limit && pd->dir == (s)->direction) { \
+ if (pf_check_threshold(&(s)->rule->pktrate)) { \
+ s = NULL; \
+ return (PF_DROP); \
+ } \
+ } \
if (PACKET_LOOPED(pd)) \
return (PF_PASS); \
} while (0)
@@ -5606,6 +5612,11 @@
pf_osfp_fingerprint(pd, ctx->th),
r->os_fingerprint)),
TAILQ_NEXT(r, entries));
+ /* must be last! */
+ if (r->pktrate.limit) {
+ PF_TEST_ATTRIB((pf_check_threshold(&r->pktrate)),
+ TAILQ_NEXT(r, entries));
+ }
/* FALLTHROUGH */
if (r->tag)
ctx->tag = r->tag;
diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c
--- a/sys/netpfil/pf/pf_ioctl.c
+++ b/sys/netpfil/pf/pf_ioctl.c
@@ -2156,7 +2156,6 @@
if (rule->rtableid > 0 && rule->rtableid >= rt_numfibs)
error = EBUSY;
-
#ifdef ALTQ
/* set queue IDs */
if (rule->qname[0] != 0) {
@@ -2181,6 +2180,9 @@
error = EINVAL;
if (!rule->log)
rule->logif = 0;
+ if (! pf_init_threshold(&rule->pktrate, rule->pktrate.limit,
+ rule->pktrate.seconds))
+ error = ENOMEM;
if (pf_addr_setup(ruleset, &rule->src.addr, rule->af))
error = ENOMEM;
if (pf_addr_setup(ruleset, &rule->dst.addr, rule->af))
diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h
--- a/sys/netpfil/pf/pf_nl.h
+++ b/sys/netpfil/pf/pf_nl.h
@@ -278,6 +278,7 @@
PF_RT_SRC_NODES_LIMIT = 79, /* u64 */
PF_RT_SRC_NODES_NAT = 80, /* u64 */
PF_RT_SRC_NODES_ROUTE = 81, /* u64 */
+ PF_RT_PKTRATE = 82, /* nested, pf_threshold_type_t */
};
enum pf_addrule_type_t {
diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c
--- a/sys/netpfil/pf/pf_nl.c
+++ b/sys/netpfil/pf/pf_nl.c
@@ -51,6 +51,9 @@
#include <netlink/netlink_debug.h>
_DECLARE_DEBUG(LOG_DEBUG);
+static bool nlattr_add_pf_threshold(struct nl_writer *, int,
+ struct pf_kthreshold *);
+
struct nl_parsed_state {
uint8_t version;
uint32_t id;
@@ -679,6 +682,14 @@
return (true);
}
+#define _OUT(_field) offsetof(struct pf_kthreshold, _field)
+static const struct nlattr_parser nla_p_threshold[] = {
+ { .type = PF_TH_LIMIT, .off = _OUT(limit), .cb = nlattr_get_uint32 },
+ { .type = PF_TH_SECONDS, .off = _OUT(seconds), .cb = nlattr_get_uint32 },
+};
+NL_DECLARE_ATTR_PARSER(threshold_parser, nla_p_threshold);
+#undef _OUT
+
#define _OUT(_field) offsetof(struct pf_krule, _field)
static const struct nlattr_parser nla_p_rule[] = {
{ .type = PF_RT_SRC, .off = _OUT(src), .arg = &rule_addr_parser,.cb = nlattr_get_nested },
@@ -749,6 +760,7 @@
{ .type = PF_RT_NAF, .off = _OUT(naf), .cb = nlattr_get_uint8 },
{ .type = PF_RT_RPOOL_RT, .off = _OUT(route), .arg = &pool_parser, .cb = nlattr_get_nested },
{ .type = PF_RT_RCV_IFNOT, .off = _OUT(rcvifnot), .cb = nlattr_get_bool },
+ { .type = PF_RT_PKTRATE, .off = _OUT(pktrate), .arg = &threshold_parser, .cb = nlattr_get_nested },
};
NL_DECLARE_ATTR_PARSER(rule_parser, nla_p_rule);
#undef _OUT
@@ -1003,6 +1015,7 @@
nlattr_add_u64(nw, PF_RT_SRC_NODES_LIMIT, counter_u64_fetch(rule->src_nodes[PF_SN_LIMIT]));
nlattr_add_u64(nw, PF_RT_SRC_NODES_NAT, counter_u64_fetch(rule->src_nodes[PF_SN_NAT]));
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);
error = pf_kanchor_copyout(ruleset, rule, anchor_call, sizeof(anchor_call));
MPASS(error == 0);

File Metadata

Mime Type
text/plain
Expires
Tue, Mar 3, 2:47 AM (18 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
29179119
Default Alt Text
D50798.id158743.diff (11 KB)

Event Timeline