Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F146365306
D50798.id158743.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
11 KB
Referenced Files
None
Subscribers
None
D50798.id158743.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D50798: pf: add a generic packet rate matching filter
Attached
Detach File
Event Timeline
Log In to Comment