diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -464,4 +464,6 @@ int flags); int pfctl_table_get_addrs(int dev, struct pfr_table *tbl, struct pfr_addr *addr, int *size, int flags); +int pfctl_set_statusif(struct pfctl_handle *h, const char *ifname); + #endif diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -2218,3 +2218,35 @@ *size = io.pfrio_size; return (0); } + +int +pfctl_set_statusif(struct pfctl_handle *h, const char *ifname) +{ + struct snl_writer nw; + struct snl_errmsg_data e = {}; + struct nlmsghdr *hdr; + uint32_t seq_id; + int family_id; + + family_id = snl_get_genl_family(&h->ss, PFNL_FAMILY_NAME); + if (family_id == 0) + return (ENOTSUP); + + snl_init_writer(&h->ss, &nw); + hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_SET_STATUSIF); + + snl_add_msg_attr_string(&nw, PF_SS_IFNAME, ifname); + + if ((hdr = snl_finalize_msg(&nw)) == NULL) + return (ENXIO); + + seq_id = hdr->nlmsg_seq; + + if (! snl_send_message(&h->ss, hdr)) + return (ENXIO); + + while ((hdr = snl_read_reply_multi(&h->ss, seq_id, &e)) != NULL) { + } + + return (e.error); +} diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -2547,19 +2547,11 @@ int pfctl_load_logif(struct pfctl *pf, char *ifname) { - struct pfioc_if pi; - - memset(&pi, 0, sizeof(pi)); - if (ifname && strlcpy(pi.ifname, ifname, - sizeof(pi.ifname)) >= sizeof(pi.ifname)) { + if (ifname != NULL && strlen(ifname) >= IFNAMSIZ) { warnx("pfctl_load_logif: strlcpy"); return (1); } - if (ioctl(pf->dev, DIOCSETSTATUSIF, &pi)) { - warnx("DIOCSETSTATUSIF"); - return (1); - } - return (0); + return (pfctl_set_statusif(pfh, ifname ? ifname : "")); } int diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h --- a/sys/netpfil/pf/pf_nl.h +++ b/sys/netpfil/pf/pf_nl.h @@ -45,6 +45,7 @@ PFNL_CMD_GETRULE = 7, PFNL_CMD_CLRSTATES = 8, PFNL_CMD_KILLSTATES = 9, + PFNL_CMD_SET_STATUSIF = 10, __PFNL_CMD_MAX, }; #define PFNL_CMD_MAX (__PFNL_CMD_MAX -1) @@ -281,6 +282,10 @@ PF_CS_KILLED = 13, /* u32 */ }; +enum pf_set_statusif_types_t { + PF_SS_UNSPEC, + PF_SS_IFNAME = 1, /* string */ +}; #ifdef _KERNEL void pf_nl_register(void); diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c --- a/sys/netpfil/pf/pf_nl.c +++ b/sys/netpfil/pf/pf_nl.c @@ -1079,11 +1079,42 @@ return (pf_handle_killclear_states(hdr, npt, PFNL_CMD_KILLSTATES)); } +struct nl_parsed_set_statusif { + char ifname[IFNAMSIZ]; +}; +#define _IN(_field) offsetof(struct genlmsghdr, _field) +#define _OUT(_field) offsetof(struct nl_parsed_set_statusif, _field) +static const struct nlattr_parser nla_p_set_statusif[] = { + { .type = PF_SS_IFNAME, .off = _OUT(ifname), .arg = (const void *)IFNAMSIZ, .cb = nlattr_get_chara }, +}; +static const struct nlfield_parser nlf_p_set_statusif[] = {}; +#undef _IN +#undef _OUT +NL_DECLARE_PARSER(set_statusif_parser, struct genlmsghdr, nlf_p_set_statusif, nla_p_set_statusif); + +static int +pf_handle_set_statusif(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + int error; + struct nl_parsed_set_statusif attrs = {}; + + error = nl_parse_nlmsg(hdr, &set_statusif_parser, npt, &attrs); + if (error != 0) + return (error); + + PF_RULES_WLOCK(); + strlcpy(V_pf_status.ifname, attrs.ifname, IFNAMSIZ); + PF_RULES_WUNLOCK(); + + return (0); +} + static const struct nlhdr_parser *all_parsers[] = { &state_parser, &addrule_parser, &getrules_parser, &clear_states_parser, + &set_statusif_parser, }; static int family_id; @@ -1152,6 +1183,13 @@ .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, + { + .cmd_num = PFNL_CMD_SET_STATUSIF, + .cmd_name = "SETSTATUSIF", + .cmd_cb = pf_handle_set_statusif, + .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NETINET_PF, + } }; void diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -16,6 +16,7 @@ fragmentation_no_reassembly \ get_state \ icmp \ + loginterface \ killstate \ macro \ map_e \ diff --git a/tests/sys/netpfil/pf/loginterface.sh b/tests/sys/netpfil/pf/loginterface.sh new file mode 100644 --- /dev/null +++ b/tests/sys/netpfil/pf/loginterface.sh @@ -0,0 +1,87 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2024 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 loginterface test' + atf_set require.user root +} + +basic_body() +{ + pft_init + + epair=$(vnet_mkepair) + + ifconfig ${epair}a 192.0.2.2/24 up + + vnet_mkjail alcatraz ${epair}b + jexec alcatraz ifconfig ${epair}b 192.0.2.1/24 up + + # Sanity check + atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 + + # No interface stats until we configure a loginterface + atf_check -o not-match:"Interface Stats for" \ + jexec alcatraz pfctl -s info + + jexec alcatraz pfctl -e + pft_set_rules alcatraz \ + "set loginterface ${epair}b" \ + "pass" + + # We do get Interface Stats listed when we've configured a loginterface + atf_check -o match:"Interface Stats for ${epair}b" \ + jexec alcatraz pfctl -s info + + # And after we've sent traffic there's non-zero counters + atf_check -s exit:0 -o ignore ping -c 1 192.0.2.1 + + atf_check -o match:"Interface Stats for ${epair}b" \ + jexec alcatraz pfctl -s info + atf_check -o match:"Passed 1" \ + jexec alcatraz pfctl -s info + + # And no interface stats once we remove the loginterface + pft_set_rules alcatraz \ + "pass" + atf_check -o not-match:"Interface Stats for ${epair}b" \ + jexec alcatraz pfctl -s info +} + +basic_cleanup() +{ + pft_cleanup +} + +atf_init_test_cases() +{ + atf_add_test_case "basic" +}