Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 27 Jun 2025 15:16:08 GMT
From:      Kristof Provost <kp@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 41fd03c08f67 - main - pf: add 'max-pkt-size'
Message-ID:  <202506271516.55RFG8Ia048982@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
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"
+}



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202506271516.55RFG8Ia048982>