git: 41fd03c08f67 - main - pf: add 'max-pkt-size'
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Fri, 27 Jun 2025 15:16:08 UTC
The branch main has been updated by kp:
URL: https://cgit.FreeBSD.org/src/commit/?id=41fd03c08f67fc9c891f4fb0ebf912658f30f212
commit 41fd03c08f67fc9c891f4fb0ebf912658f30f212
Author: Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2025-06-06 14:45:43 +0000
Commit: Kristof Provost <kp@FreeBSD.org>
CommitDate: 2025-06-27 14:55:15 +0000
pf: add 'max-pkt-size'
Allow pf to limit packets to a specified maximum size. This applies to all
packets, and if reassembly is enabled, looks at the reassembled size, not the
size of individual fragments.
Sponsored by: Rubicon Communications, LLC ("Netgate")
---
lib/libpfctl/libpfctl.c | 2 +
lib/libpfctl/libpfctl.h | 1 +
sbin/pfctl/parse.y | 13 +++++-
sbin/pfctl/pfctl_parser.c | 2 +
sbin/pfctl/tests/files/pf1069.in | 1 +
sbin/pfctl/tests/files/pf1069.ok | 1 +
sbin/pfctl/tests/pfctl_test_list.inc | 1 +
share/man/man5/pf.conf.5 | 4 ++
sys/net/pfvar.h | 1 +
sys/netpfil/pf/pf.c | 8 ++++
sys/netpfil/pf/pf_nl.c | 2 +
sys/netpfil/pf/pf_nl.h | 1 +
tests/sys/netpfil/pf/Makefile | 1 +
tests/sys/netpfil/pf/max_pkt_size.sh | 85 ++++++++++++++++++++++++++++++++++++
14 files changed, 122 insertions(+), 1 deletion(-)
diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index 4789448d2a37..e4123fe02211 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -1251,6 +1251,7 @@ snl_add_msg_attr_pf_rule(struct snl_writer *nw, uint32_t type, const struct pfct
snl_add_msg_attr_u32(nw, PF_RT_MAX_SRC_CONN, r->max_src_conn);
snl_add_msg_attr_u32(nw, PF_RT_MAX_SRC_CONN_RATE_LIMIT, r->max_src_conn_rate.limit);
snl_add_msg_attr_u32(nw, PF_RT_MAX_SRC_CONN_RATE_SECS, r->max_src_conn_rate.seconds);
+ snl_add_msg_attr_u16(nw, PF_RT_MAX_PKT_SIZE, r->max_pkt_size);
snl_add_msg_attr_u16(nw, PF_RT_DNPIPE, r->dnpipe);
snl_add_msg_attr_u16(nw, PF_RT_DNRPIPE, r->dnrpipe);
@@ -1692,6 +1693,7 @@ static struct snl_attr_parser ap_getrule[] = {
{ .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 },
+ { .type = PF_RT_MAX_PKT_SIZE, .off =_OUT(r.max_pkt_size), .cb = snl_attr_get_uint16 },
};
#undef _OUT
SNL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, snl_f_p_empty, ap_getrule);
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index 7de7a08e90bf..98a80758ca74 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -211,6 +211,7 @@ struct pfctl_rule {
uint32_t limit;
uint32_t seconds;
} max_src_conn_rate;
+ uint16_t max_pkt_size;
uint32_t qid;
uint32_t pqid;
uint16_t dnpipe;
diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y
index e0bd5ce4aee0..257a62df76f4 100644
--- a/sbin/pfctl/parse.y
+++ b/sbin/pfctl/parse.y
@@ -312,6 +312,7 @@ static struct filter_opts {
uint32_t limit;
uint32_t seconds;
} pktrate;
+ int max_pkt_size;
} filter_opts;
static struct antispoof_opts {
@@ -535,7 +536,7 @@ int parseport(char *, struct range *r, int);
%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 MAXPKTRATE
+%token BINATTO MAXPKTRATE MAXPKTSIZE
%token <v.string> STRING
%token <v.number> NUMBER
%token <v.i> PORTBINARY
@@ -1020,6 +1021,7 @@ anchorrule : ANCHOR anchorname dir quick interface af proto fromto
r.ridentifier = $9.ridentifier;
r.pktrate.limit = $9.pktrate.limit;
r.pktrate.seconds = $9.pktrate.seconds;
+ r.max_pkt_size = $9.max_pkt_size;
if ($9.tag)
if (strlcpy(r.tagname, $9.tag,
@@ -2499,6 +2501,7 @@ pfrule : action dir logquick interface route af proto fromto
r.keep_state = $9.keep.action;
r.pktrate.limit = $9.pktrate.limit;
r.pktrate.seconds = $9.pktrate.seconds;
+ r.max_pkt_size = $9.max_pkt_size;
o = $9.keep.options;
/* 'keep state' by default on pass rules. */
@@ -3135,6 +3138,13 @@ filter_opt : USER uids {
filter_opts.pktrate.limit = $2;
filter_opts.pktrate.seconds = $4;
}
+ | MAXPKTSIZE NUMBER {
+ if ($2 < 0 || $2 > UINT16_MAX) {
+ yyerror("only positive values permitted");
+ YYERROR;
+ }
+ filter_opts.max_pkt_size = $2;
+ }
| filter_sets
;
@@ -6721,6 +6731,7 @@ lookup(char *s)
{ "max", MAXIMUM},
{ "max-mss", MAXMSS},
{ "max-pkt-rate", MAXPKTRATE},
+ { "max-pkt-size", MAXPKTSIZE},
{ "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
index 32e98eb20b7c..dbc7ff00782f 100644
--- a/sbin/pfctl/pfctl_parser.c
+++ b/sbin/pfctl/pfctl_parser.c
@@ -1010,6 +1010,8 @@ print_rule(struct pfctl_rule *r, const char *anchor_call, int verbose, int numer
if (r->pktrate.limit)
printf(" max-pkt-rate %u/%u", r->pktrate.limit,
r->pktrate.seconds);
+ if (r->max_pkt_size)
+ printf( " max-pkt-size %u", r->max_pkt_size);
if (r->scrub_flags & PFSTATE_SETMASK) {
char *comma = "";
printf(" set (");
diff --git a/sbin/pfctl/tests/files/pf1069.in b/sbin/pfctl/tests/files/pf1069.in
new file mode 100644
index 000000000000..3a69158fff7e
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1069.in
@@ -0,0 +1 @@
+pass in proto icmp max-pkt-size 128
diff --git a/sbin/pfctl/tests/files/pf1069.ok b/sbin/pfctl/tests/files/pf1069.ok
new file mode 100644
index 000000000000..b79228266156
--- /dev/null
+++ b/sbin/pfctl/tests/files/pf1069.ok
@@ -0,0 +1 @@
+pass in proto icmp all max-pkt-size 128 keep state
diff --git a/sbin/pfctl/tests/pfctl_test_list.inc b/sbin/pfctl/tests/pfctl_test_list.inc
index 2b237ed0922e..f798fedb70a8 100644
--- a/sbin/pfctl/tests/pfctl_test_list.inc
+++ b/sbin/pfctl/tests/pfctl_test_list.inc
@@ -177,3 +177,4 @@ PFCTL_TEST(1065, "no nat")
PFCTL_TEST(1066, "no rdr")
PFCTL_TEST_FAIL(1067, "route-to can't be used on block rules")
PFCTL_TEST(1068, "max-pkt-rate")
+PFCTL_TEST(1069, "max-pkt-size")
diff --git a/share/man/man5/pf.conf.5 b/share/man/man5/pf.conf.5
index 49c81f51294c..5d802f81984f 100644
--- a/share/man/man5/pf.conf.5
+++ b/share/man/man5/pf.conf.5
@@ -2227,6 +2227,9 @@ pass in proto icmp max-pkt-rate 100/10
When the rate is exceeded, all ICMP is blocked until the rate falls below
100 per 10 seconds again.
.Pp
+.It Ar max-pkt-size Aq Ar number
+Limit each packet to be no more than the specified number of bytes.
+This includes the IP header, but not any layer 2 header.
.It Xo Ar queue Aq Ar queue
.No \*(Ba ( Aq Ar queue ,
.Aq Ar queue )
@@ -3401,6 +3404,7 @@ filteropt = user | group | flags | icmp-type | icmp6-type | "tos" tos |
"label" string | "tag" string | [ "!" ] "tagged" string |
"max-pkt-rate" number "/" seconds |
"set prio" ( number | "(" number [ [ "," ] number ] ")" ) |
+ "max-pkt-size" number |
"queue" ( string | "(" string [ [ "," ] string ] ")" ) |
"rtable" number | "probability" number"%" | "prio" number |
"dnpipe" ( number | "(" number "," number ")" ) |
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 33574dbd5c2a..5c798216f9f5 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -845,6 +845,7 @@ struct pf_krule {
u_int32_t limit;
u_int32_t seconds;
} max_src_conn_rate;
+ uint16_t max_pkt_size;
u_int16_t qid;
u_int16_t pqid;
u_int16_t dnpipe;
diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 6533b06c5d9d..6da537aaa2cd 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -10677,6 +10677,14 @@ done:
("pf: dropping packet with dangerous headers\n"));
}
+ if (r && r->max_pkt_size && pd.tot_len > r->max_pkt_size) {
+ action = PF_DROP;
+ REASON_SET(&reason, PFRES_NORM);
+ pd.act.log = PF_LOG_FORCE;
+ DPFPRINTF(PF_DEBUG_MISC,
+ ("pf: dropping overly long packet\n"));
+ }
+
if (s) {
uint8_t log = pd.act.log;
memcpy(&pd.act, &s->act, sizeof(struct pf_rule_actions));
diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c
index 48cba96b04b0..381e966eacf1 100644
--- a/sys/netpfil/pf/pf_nl.c
+++ b/sys/netpfil/pf/pf_nl.c
@@ -761,6 +761,7 @@ static const struct nlattr_parser nla_p_rule[] = {
{ .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 },
+ { .type = PF_RT_MAX_PKT_SIZE, .off = _OUT(max_pkt_size), .cb = nlattr_get_uint16 },
};
NL_DECLARE_ATTR_PARSER(rule_parser, nla_p_rule);
#undef _OUT
@@ -945,6 +946,7 @@ pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt)
nlattr_add_u32(nw, PF_RT_MAX_SRC_CONN, rule->max_src_conn);
nlattr_add_u32(nw, PF_RT_MAX_SRC_CONN_RATE_LIMIT, rule->max_src_conn_rate.limit);
nlattr_add_u32(nw, PF_RT_MAX_SRC_CONN_RATE_SECS, rule->max_src_conn_rate.seconds);
+ nlattr_add_u16(nw, PF_RT_MAX_PKT_SIZE, rule->max_pkt_size);
nlattr_add_u16(nw, PF_RT_DNPIPE, rule->dnpipe);
nlattr_add_u16(nw, PF_RT_DNRPIPE, rule->dnrpipe);
diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h
index 97ef574995f5..929c20e4c582 100644
--- a/sys/netpfil/pf/pf_nl.h
+++ b/sys/netpfil/pf/pf_nl.h
@@ -279,6 +279,7 @@ enum pf_rule_type_t {
PF_RT_SRC_NODES_NAT = 80, /* u64 */
PF_RT_SRC_NODES_ROUTE = 81, /* u64 */
PF_RT_PKTRATE = 82, /* nested, pf_threshold_type_t */
+ PF_RT_MAX_PKT_SIZE = 83, /* u16 */
};
enum pf_addrule_type_t {
diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile
index fe2740ed0e7f..3adaef09ddbd 100644
--- a/tests/sys/netpfil/pf/Makefile
+++ b/tests/sys/netpfil/pf/Makefile
@@ -23,6 +23,7 @@ ATF_TESTS_SH+= altq \
macro \
match \
max_pkt_rate \
+ max_pkt_size \
max_states \
mbuf \
modulate \
diff --git a/tests/sys/netpfil/pf/max_pkt_size.sh b/tests/sys/netpfil/pf/max_pkt_size.sh
new file mode 100644
index 000000000000..05aab0b7990a
--- /dev/null
+++ b/tests/sys/netpfil/pf/max_pkt_size.sh
@@ -0,0 +1,85 @@
+#
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2025 Rubicon Communications, LLC (Netgate)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+. $(atf_get_srcdir)/utils.subr
+
+atf_test_case "basic" "cleanup"
+basic_head()
+{
+ atf_set descr 'Basic max-pkt-size test'
+ atf_set require.user root
+}
+
+basic_body()
+{
+ pft_init
+
+ epair=$(vnet_mkepair)
+
+ ifconfig ${epair}b 192.0.2.2/24 up
+
+ vnet_mkjail alcatraz ${epair}a
+ jexec alcatraz ifconfig ${epair}a 192.0.2.1/24 up
+
+ jexec alcatraz pfctl -e
+ pft_set_rules alcatraz \
+ "pass max-pkt-size 128"
+
+ # Small packets pass
+ atf_check -s exit:0 -o ignore \
+ ping -c 1 192.0.2.1
+ atf_check -s exit:0 -o ignore \
+ ping -c 1 -s 100 192.0.2.1
+
+ # Larger packets do not
+ atf_check -s exit:2 -o ignore \
+ ping -c 3 -s 101 192.0.2.1
+ atf_check -s exit:2 -o ignore \
+ ping -c 3 -s 128 192.0.2.1
+
+ # We can enforce this on fragmented packets too
+ pft_set_rules alcatraz \
+ "pass max-pkt-size 2000"
+
+ atf_check -s exit:0 -o ignore \
+ ping -c 1 -s 1400 192.0.2.1
+ atf_check -s exit:0 -o ignore \
+ ping -c 1 -s 1972 192.0.2.1
+ atf_check -s exit:2 -o ignore \
+ ping -c 1 -s 1973 192.0.2.1
+ atf_check -s exit:2 -o ignore \
+ ping -c 3 -s 3000 192.0.2.1
+}
+
+basic_cleanup()
+{
+ pft_cleanup
+}
+
+atf_init_test_cases()
+{
+ atf_add_test_case "basic"
+}