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
