The branch main has been updated by kp:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=726ff260ecfa38878aec982456c44ddb0f9c791b

commit 726ff260ecfa38878aec982456c44ddb0f9c791b
Author:     Kristof Provost <[email protected]>
AuthorDate: 2026-05-05 12:42:16 +0000
Commit:     Kristof Provost <[email protected]>
CommitDate: 2026-05-07 15:06:56 +0000

    pfctl: optionally print the rule in the state overview
    
    When dumping states optionally (at '-vv') also show the rule which
    created the state. This can be helpful if the ruleset changed and we
    want to know what rule created the state.
    
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
---
 lib/libpfctl/libpfctl.c           |   4 +
 lib/libpfctl/libpfctl.h           |   2 +
 sbin/pfctl/pf_print_state.c       |   4 +
 sbin/pfctl/pfctl.c                |   3 +
 sys/netpfil/pf/pf_nl.c            | 189 +++++++++++++++++++++++---------------
 sys/netpfil/pf/pf_nl.h            |   2 +
 tests/sys/netpfil/pf/get_state.sh |  44 +++++++++
 tests/sys/netpfil/pf/killstate.sh |   2 +-
 8 files changed, 173 insertions(+), 77 deletions(-)

diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c
index ac60a924c228..4e51167b401a 100644
--- a/lib/libpfctl/libpfctl.c
+++ b/lib/libpfctl/libpfctl.c
@@ -1929,6 +1929,8 @@ static const struct snl_attr_parser nla_p_skey[] = {
 SNL_DECLARE_ATTR_PARSER(skey_parser, nla_p_skey);
 #undef _OUT
 
+SNL_DECLARE_ATTR_PARSER(rule_parser, ap_getrule);
+
 #define        _IN(_field)     offsetof(struct genlmsghdr, _field)
 #define        _OUT(_field)    offsetof(struct pfctl_state, _field)
 static struct snl_attr_parser ap_state[] = {
@@ -1963,6 +1965,7 @@ static struct snl_attr_parser ap_state[] = {
        { .type = PF_ST_RT_IFNAME, .off = _OUT(rt_ifname), .cb = 
snl_attr_store_ifname },
        { .type = PF_ST_SRC_NODE_FLAGS, .off = _OUT(src_node_flags), .cb = 
snl_attr_get_uint8 },
        { .type = PF_ST_RT_AF, .off = _OUT(rt_af), .cb = snl_attr_get_uint8 },
+       { .type = PF_ST_CREATED_BY_RULE, .off = _OUT(created_by_rule), .arg = 
&rule_parser, .cb = snl_attr_get_nested },
 };
 #undef _IN
 #undef _OUT
@@ -1988,6 +1991,7 @@ pfctl_get_states_h(struct pfctl_handle *h, struct 
pfctl_state_filter *filter, pf
        snl_add_msg_attr_u8(&nw, PF_ST_AF, filter->af);
        snl_add_msg_attr_ip6(&nw, PF_ST_FILTER_ADDR, &filter->addr.v6);
        snl_add_msg_attr_ip6(&nw, PF_ST_FILTER_MASK, &filter->mask.v6);
+       snl_add_msg_attr_bool(&nw, PF_ST_INCLUDE_RULE, filter->include_rule);
 
        hdr = snl_finalize_msg(&nw);
        if (hdr == NULL)
diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h
index 1012be53db65..3080209ec7a0 100644
--- a/lib/libpfctl/libpfctl.h
+++ b/lib/libpfctl/libpfctl.h
@@ -406,6 +406,7 @@ struct pfctl_state {
        char                     rt_ifname[IFNAMSIZ];
        sa_family_t              rt_af;
        uint8_t                  src_node_flags;
+       struct pfctl_rule        created_by_rule;
 };
 
 TAILQ_HEAD(pfctl_statelist, pfctl_state);
@@ -504,6 +505,7 @@ struct pfctl_state_filter {
        sa_family_t             af;
        struct pf_addr          addr;
        struct pf_addr          mask;
+       bool                    include_rule;
 };
 typedef int (*pfctl_get_state_fn)(struct pfctl_state *, void *);
 int pfctl_get_states_iter(pfctl_get_state_fn f, void *arg);
diff --git a/sbin/pfctl/pf_print_state.c b/sbin/pfctl/pf_print_state.c
index 1c5a46f86b35..9b0c57fd73d3 100644
--- a/sbin/pfctl/pf_print_state.c
+++ b/sbin/pfctl/pf_print_state.c
@@ -434,6 +434,10 @@ print_state(struct pfctl_state *s, int opts)
 
                if (strcmp(s->ifname, s->orig_ifname) != 0)
                        printf("   origif: %s\n", s->orig_ifname);
+
+               printf("   rule: ");
+               print_rule(&s->created_by_rule, "", 0, 0);
+               printf("\n");
        }
 }
 
diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c
index f35baf25ec35..c349487ed9d2 100644
--- a/sbin/pfctl/pfctl.c
+++ b/sbin/pfctl/pfctl.c
@@ -1996,6 +1996,9 @@ pfctl_show_states(int dev, const char *iface, int opts)
        if (iface != NULL)
                strlcpy(filter.ifname, iface, IFNAMSIZ);
 
+       if (opts & PF_OPT_VERBOSE2)
+               filter.include_rule = true;
+
        arg.opts = opts;
        arg.dotitle = opts & PF_OPT_SHOWALL;
        arg.iface = iface;
diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c
index e4ce9e64f637..d1beb7681c21 100644
--- a/sys/netpfil/pf/pf_nl.c
+++ b/sys/netpfil/pf/pf_nl.c
@@ -51,8 +51,11 @@
 #include <netlink/netlink_debug.h>
 _DECLARE_DEBUG(LOG_DEBUG);
 
+static bool nlattr_add_labels(struct nl_writer *nw, int attrtype,
+    const struct pf_krule *r);
+static bool nlattr_add_rule(struct nl_writer *nw, const struct pf_krule *rule);
 static bool nlattr_add_pf_threshold(struct nl_writer *, int,
-    struct pf_kthreshold *);
+    const struct pf_kthreshold *);
 
 struct nl_parsed_state {
        uint8_t         version;
@@ -63,6 +66,7 @@ struct nl_parsed_state {
        sa_family_t     af;
        struct pf_addr  addr;
        struct pf_addr  mask;
+       bool            include_rule;
 };
 
 #define        _IN(_field)     offsetof(struct genlmsghdr, _field)
@@ -75,6 +79,7 @@ static const struct nlattr_parser nla_p_state[] = {
        { .type = PF_ST_PROTO, .off = _OUT(proto), .cb = nlattr_get_uint16 },
        { .type = PF_ST_FILTER_ADDR, .off = _OUT(addr), .cb = 
nlattr_get_in6_addr },
        { .type = PF_ST_FILTER_MASK, .off = _OUT(mask), .cb = 
nlattr_get_in6_addr },
+       { .type = PF_ST_INCLUDE_RULE, .off = _OUT(include_rule), .cb = 
nlattr_get_bool },
 };
 static const struct nlfield_parser nlf_p_generic[] = {
        { .off_in = _IN(version), .off_out = _OUT(version), .cb = nlf_get_u8 },
@@ -146,8 +151,26 @@ dump_state_key(struct nl_writer *nw, int attr, const 
struct pf_state_key *key)
        return (true);
 }
 
+static bool
+nlattr_add_rule_nested(struct nl_writer *nw, int attr, const struct pf_krule 
*r)
+{
+       int off;
+       bool ret;
+
+       off = nlattr_add_nested(nw, attr);
+       if (off == 0)
+               return (false);
+
+       ret = nlattr_add_rule(nw, r);
+
+       nlattr_set_len(nw, off);
+
+       return (ret);
+}
+
 static int
-dump_state(struct nlpcb *nlp, const struct nlmsghdr *hdr, struct pf_kstate *s,
+dump_state(struct nlpcb *nlp, const struct nlmsghdr *hdr,
+    struct nl_parsed_state *attrs, struct pf_kstate *s,
     struct nl_pstate *npt)
 {
        struct nl_writer *nw = npt->nw;
@@ -231,6 +254,9 @@ dump_state(struct nlpcb *nlp, const struct nlmsghdr *hdr, 
struct pf_kstate *s,
        if (!dump_state_peer(nw, PF_ST_PEER_DST, &s->dst))
                goto enomem;
 
+       if (attrs->include_rule && s->rule != NULL)
+               nlattr_add_rule_nested(nw, PF_ST_CREATED_BY_RULE, s->rule);
+
        if (nlmsg_end(nw))
                return (0);
 
@@ -282,7 +308,7 @@ handle_dumpstates(struct nlpcb *nlp, struct nl_parsed_state 
*attrs,
                            &attrs->mask, &attrs->addr, af))
                                continue;
 
-                       error = dump_state(nlp, hdr, s, npt);
+                       error = dump_state(nlp, hdr, attrs, s, npt);
                        if (error != 0)
                                break;
                }
@@ -307,7 +333,7 @@ handle_getstate(struct nlpcb *nlp, struct nl_parsed_state 
*attrs,
        s = pf_find_state_byid(attrs->id, attrs->creatorid);
        if (s == NULL)
                return (ENOENT);
-       ret = dump_state(nlp, hdr, s, npt);
+       ret = dump_state(nlp, hdr, attrs, s, npt);
        PF_STATE_UNLOCK(s);
 
        return (ret);
@@ -465,7 +491,8 @@ NL_DECLARE_ATTR_PARSER(rule_addr_parser, nla_p_ruleaddr);
 #undef _OUT
 
 static bool
-nlattr_add_rule_addr(struct nl_writer *nw, int attrtype, struct pf_rule_addr 
*r)
+nlattr_add_rule_addr(struct nl_writer *nw, int attrtype,
+    const struct pf_rule_addr *r)
 {
        struct pf_addr_wrap aw = {0};
        int off = nlattr_add_nested(nw, attrtype);
@@ -687,7 +714,8 @@ nlattr_get_nested_timeouts(struct nlattr *nla, struct 
nl_pstate *npt, const void
 }
 
 static bool
-nlattr_add_timeout(struct nl_writer *nw, int attrtype, uint32_t *timeout)
+nlattr_add_timeout(struct nl_writer *nw, int attrtype,
+    const uint32_t *timeout)
 {
        int off = nlattr_add_nested(nw, attrtype);
 
@@ -875,76 +903,10 @@ out:
        return (error);
 }
 
-struct nl_parsed_get_rule {
-       char anchor[MAXPATHLEN];
-       uint8_t action;
-       uint32_t nr;
-       uint32_t ticket;
-       uint8_t clear;
-};
-#define        _OUT(_field)    offsetof(struct nl_parsed_get_rule, _field)
-static const struct nlattr_parser nla_p_getrule[] = {
-       { .type = PF_GR_ANCHOR, .off = _OUT(anchor), .arg = (void *)MAXPATHLEN, 
.cb = nlattr_get_chara },
-       { .type = PF_GR_ACTION, .off = _OUT(action), .cb = nlattr_get_uint8 },
-       { .type = PF_GR_NR, .off = _OUT(nr), .cb = nlattr_get_uint32 },
-       { .type = PF_GR_TICKET, .off = _OUT(ticket), .cb = nlattr_get_uint32 },
-       { .type = PF_GR_CLEAR, .off = _OUT(clear), .cb = nlattr_get_uint8 },
-};
-#undef _OUT
-NL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, nlf_p_empty, 
nla_p_getrule);
-
-static int
-pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt)
+static bool
+nlattr_add_rule(struct nl_writer *nw, const struct pf_krule *rule)
 {
-       char                             anchor_call[MAXPATHLEN];
-       struct nl_parsed_get_rule        attrs = {};
-       struct nl_writer                *nw = npt->nw;
-       struct genlmsghdr               *ghdr_new;
-       struct pf_kruleset              *ruleset;
-       struct pf_krule                 *rule;
-       u_int64_t                        src_nodes_total = 0;
-       int                              rs_num;
-       int                              error;
-
-       error = nl_parse_nlmsg(hdr, &getrule_parser, npt, &attrs);
-       if (error != 0)
-               return (error);
-
-       if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr)))
-               return (ENOMEM);
-
-       ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr);
-       ghdr_new->cmd = PFNL_CMD_GETRULE;
-
-       PF_RULES_WLOCK();
-       ruleset = pf_find_kruleset(attrs.anchor);
-       if (ruleset == NULL) {
-               PF_RULES_WUNLOCK();
-               error = ENOENT;
-               goto out;
-       }
-
-       rs_num = pf_get_ruleset_number(attrs.action);
-       if (rs_num >= PF_RULESET_MAX) {
-               PF_RULES_WUNLOCK();
-               error = EINVAL;
-               goto out;
-       }
-
-       if (attrs.ticket != ruleset->rules[rs_num].active.ticket) {
-               PF_RULES_WUNLOCK();
-               error = EBUSY;
-               goto out;
-       }
-
-       rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr);
-       while ((rule != NULL) && (rule->nr != attrs.nr))
-               rule = TAILQ_NEXT(rule, entries);
-       if (rule == NULL) {
-               PF_RULES_WUNLOCK();
-               error = EBUSY;
-               goto out;
-       }
+       u_int64_t src_nodes_total = 0;
 
        nlattr_add_rule_addr(nw, PF_RT_SRC, &rule->src);
        nlattr_add_rule_addr(nw, PF_RT_DST, &rule->dst);
@@ -1050,6 +1012,81 @@ pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate 
*npt)
        nlattr_add_u8(nw, PF_RT_SOURCE_LIMIT, rule->sourcelim.id);
        nlattr_add_u32(nw, PF_RT_SOURCE_LIMIT_ACTION, 
rule->sourcelim.limiter_action);
 
+       return (true);
+}
+
+struct nl_parsed_get_rule {
+       char anchor[MAXPATHLEN];
+       uint8_t action;
+       uint32_t nr;
+       uint32_t ticket;
+       uint8_t clear;
+};
+#define        _OUT(_field)    offsetof(struct nl_parsed_get_rule, _field)
+static const struct nlattr_parser nla_p_getrule[] = {
+       { .type = PF_GR_ANCHOR, .off = _OUT(anchor), .arg = (void *)MAXPATHLEN, 
.cb = nlattr_get_chara },
+       { .type = PF_GR_ACTION, .off = _OUT(action), .cb = nlattr_get_uint8 },
+       { .type = PF_GR_NR, .off = _OUT(nr), .cb = nlattr_get_uint32 },
+       { .type = PF_GR_TICKET, .off = _OUT(ticket), .cb = nlattr_get_uint32 },
+       { .type = PF_GR_CLEAR, .off = _OUT(clear), .cb = nlattr_get_uint8 },
+};
+#undef _OUT
+NL_DECLARE_PARSER(getrule_parser, struct genlmsghdr, nlf_p_empty, 
nla_p_getrule);
+
+static int
+pf_handle_getrule(struct nlmsghdr *hdr, struct nl_pstate *npt)
+{
+       char                             anchor_call[MAXPATHLEN];
+       struct nl_parsed_get_rule        attrs = {};
+       struct nl_writer                *nw = npt->nw;
+       struct genlmsghdr               *ghdr_new;
+       struct pf_kruleset              *ruleset;
+       struct pf_krule                 *rule;
+       int                              rs_num;
+       int                              error;
+
+       error = nl_parse_nlmsg(hdr, &getrule_parser, npt, &attrs);
+       if (error != 0)
+               return (error);
+
+       if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr)))
+               return (ENOMEM);
+
+       ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr);
+       ghdr_new->cmd = PFNL_CMD_GETRULE;
+
+       PF_RULES_WLOCK();
+       ruleset = pf_find_kruleset(attrs.anchor);
+       if (ruleset == NULL) {
+               PF_RULES_WUNLOCK();
+               error = ENOENT;
+               goto out;
+       }
+
+       rs_num = pf_get_ruleset_number(attrs.action);
+       if (rs_num >= PF_RULESET_MAX) {
+               PF_RULES_WUNLOCK();
+               error = EINVAL;
+               goto out;
+       }
+
+       if (attrs.ticket != ruleset->rules[rs_num].active.ticket) {
+               PF_RULES_WUNLOCK();
+               error = EBUSY;
+               goto out;
+       }
+
+       rule = TAILQ_FIRST(ruleset->rules[rs_num].active.ptr);
+       while ((rule != NULL) && (rule->nr != attrs.nr))
+               rule = TAILQ_NEXT(rule, entries);
+       if (rule == NULL) {
+               PF_RULES_WUNLOCK();
+               error = EBUSY;
+               goto out;
+       }
+
+       nlattr_add_rule(nw, rule);
+
        error = pf_kanchor_copyout(ruleset, rule, anchor_call, 
sizeof(anchor_call));
        MPASS(error == 0);
 
@@ -1729,7 +1766,7 @@ pf_handle_get_ruleset(struct nlmsghdr *hdr, struct 
nl_pstate *npt)
 
 static bool
 nlattr_add_pf_threshold(struct nl_writer *nw, int attrtype,
-    struct pf_kthreshold *t)
+    const struct pf_kthreshold *t)
 {
        int      off = nlattr_add_nested(nw, attrtype);
        int      conn_rate_count = 0;
diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h
index 6591c707d9a4..4d0186ea86a5 100644
--- a/sys/netpfil/pf/pf_nl.h
+++ b/sys/netpfil/pf/pf_nl.h
@@ -152,6 +152,8 @@ enum pfstate_type_t {
        PF_ST_RT_IFNAME         = 37, /* string */
        PF_ST_SRC_NODE_FLAGS    = 38, /* u8 */
        PF_ST_RT_AF             = 39, /* u8 */
+       PF_ST_INCLUDE_RULE      = 40, /* bool */
+       PF_ST_CREATED_BY_RULE   = 41, /* nested, pf_rule_type_t */
 };
 
 enum pf_addr_type_t {
diff --git a/tests/sys/netpfil/pf/get_state.sh 
b/tests/sys/netpfil/pf/get_state.sh
index eb2bc854c800..eb2e2f37a15d 100644
--- a/tests/sys/netpfil/pf/get_state.sh
+++ b/tests/sys/netpfil/pf/get_state.sh
@@ -74,7 +74,51 @@ many_cleanup()
        pft_cleanup
 }
 
+atf_test_case "rule" "cleanup"
+rule_head()
+{
+       atf_set descr 'Test retrieving original state establishing rule'
+       atf_set require.user root
+}
+
+rule_body()
+{
+       pft_init
+
+       epair=$(vnet_mkepair)
+       ifconfig ${epair}a 192.0.2.1/24 up
+
+       vnet_mkjail alcatraz ${epair}b
+       jexec alcatraz ifconfig ${epair}b 192.0.2.2/24 up
+       jexec alcatraz pfctl -e
+
+       pft_set_rules alcatraz \
+               "pass in proto icmp label \"icmplabel\""
+
+       # Establish state
+       atf_check -o ignore ping -c 1 -W 1 192.0.2.2
+
+       # We should see the rule now
+       atf_check -o match:"rule: pass in proto icmp all keep state label 
\"icmplabel\"" \
+           -e ignore \
+           jexec alcatraz pfctl -ss -vv
+
+       pft_set_rules noflush alcatraz \
+           "pass"
+
+       # Even after the rules changes we should see the original rule
+       atf_check -o match:"rule: pass in proto icmp all keep state label 
\"icmplabel\"" \
+           -e ignore \
+           jexec alcatraz pfctl -ss -vv
+}
+
+rule_cleanup()
+{
+       pft_cleanup
+}
+
 atf_init_test_cases()
 {
        atf_add_test_case "many"
+       atf_add_test_case "rule"
 }
diff --git a/tests/sys/netpfil/pf/killstate.sh 
b/tests/sys/netpfil/pf/killstate.sh
index f5925d715e7c..161a8b7668f2 100644
--- a/tests/sys/netpfil/pf/killstate.sh
+++ b/tests/sys/netpfil/pf/killstate.sh
@@ -666,7 +666,7 @@ key_body()
                --replyif ${epair}a
 
        # Get the state key
-       key=$(jexec alcatraz pfctl -ss -vvv | awk '/icmp/ { print($2 " " $3 " " 
$4 " " $5); }')
+       key=$(jexec alcatraz pfctl -ss -vvv | awk '/all icmp/ { print($2 " " $3 
" " $4 " " $5); }')
        bad_key=$(echo ${key} | sed 's/icmp/tcp/')
 
        # Kill the wrong key

Reply via email to