The branch main has been updated by kp:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=41fd03c08f67fc9c891f4fb0ebf912658f30f212

commit 41fd03c08f67fc9c891f4fb0ebf912658f30f212
Author:     Kristof Provost <k...@freebsd.org>
AuthorDate: 2025-06-06 14:45:43 +0000
Commit:     Kristof Provost <k...@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"
+}

Reply via email to