The branch main has been updated by kp: URL: https://cgit.FreeBSD.org/src/commit/?id=d9ab8999313845e87c67532437a0441d9cd57e72
commit d9ab8999313845e87c67532437a0441d9cd57e72 Author: Kristof Provost <k...@freebsd.org> AuthorDate: 2024-06-07 01:08:50 +0000 Commit: Kristof Provost <k...@freebsd.org> CommitDate: 2024-06-07 18:59:02 +0000 pf: migrate DIOCGETLIMIT/DIOCSETLIMIT to netlink Event: Kitchener-Waterloo Hackathon 202406 --- lib/libpfctl/libpfctl.c | 88 +++++++++++++++++++++++++++++-- lib/libpfctl/libpfctl.h | 2 + sbin/pfctl/parse.y | 2 +- sbin/pfctl/pfctl.c | 19 +++---- sbin/pfctl/pfctl_parser.h | 2 +- sys/net/pfvar.h | 2 + sys/netpfil/pf/pf_ioctl.c | 56 +++++++++++++------- sys/netpfil/pf/pf_nl.c | 76 ++++++++++++++++++++++++++ sys/netpfil/pf/pf_nl.h | 8 +++ tests/sys/netpfil/pf/Makefile | 1 + tests/sys/netpfil/pf/limits.sh | 66 +++++++++++++++++++++++ usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c | 14 +++-- 12 files changed, 291 insertions(+), 45 deletions(-) diff --git a/lib/libpfctl/libpfctl.c b/lib/libpfctl/libpfctl.c index a31fe6f0aff4..ebe52e7a415f 100644 --- a/lib/libpfctl/libpfctl.c +++ b/lib/libpfctl/libpfctl.c @@ -2213,7 +2213,7 @@ pfctl_clear_eth_rules(int dev, const char *anchorname) } static int -pfctl_get_limit(int dev, const int index, uint *limit) +_pfctl_get_limit(int dev, const int index, uint *limit) { struct pfioc_limit pl; @@ -2237,7 +2237,7 @@ pfctl_set_syncookies(int dev, const struct pfctl_syncookies *s) uint state_limit; uint64_t lim, hi, lo; - ret = pfctl_get_limit(dev, PF_LIMIT_STATES, &state_limit); + ret = _pfctl_get_limit(dev, PF_LIMIT_STATES, &state_limit); if (ret != 0) return (ret); @@ -2274,7 +2274,7 @@ pfctl_get_syncookies(int dev, struct pfctl_syncookies *s) uint state_limit; bool enabled, adaptive; - ret = pfctl_get_limit(dev, PF_LIMIT_STATES, &state_limit); + ret = _pfctl_get_limit(dev, PF_LIMIT_STATES, &state_limit); if (ret != 0) return (ret); @@ -2606,3 +2606,85 @@ pfctl_get_timeout(struct pfctl_handle *h, uint32_t timeout, uint32_t *seconds) return (e.error); } +int +pfctl_set_limit(struct pfctl_handle *h, const int index, const uint limit) +{ + 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_LIMIT); + + snl_add_msg_attr_u32(&nw, PF_LI_INDEX, index); + snl_add_msg_attr_u32(&nw, PF_LI_LIMIT, limit); + + 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); +} + +struct pfctl_nl_limit { + unsigned int limit; +}; +#define _OUT(_field) offsetof(struct pfctl_nl_limit, _field) +static struct snl_attr_parser ap_get_limit[] = { + { .type = PF_LI_LIMIT, .off = _OUT(limit), .cb = snl_attr_get_uint32 }, +}; +static struct snl_field_parser fp_get_limit[] = {}; +#undef _OUT +SNL_DECLARE_PARSER(get_limit_parser, struct genlmsghdr, fp_get_limit, ap_get_limit); + +int +pfctl_get_limit(struct pfctl_handle *h, const int index, uint *limit) +{ + struct snl_writer nw; + struct pfctl_nl_limit li = {}; + 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_GET_LIMIT); + hdr->nlmsg_flags |= NLM_F_DUMP; + + snl_add_msg_attr_u32(&nw, PF_LI_INDEX, index); + + 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) { + if (! snl_parse_nlmsg(&h->ss, hdr, &get_limit_parser, &li)) + continue; + } + + if (limit != NULL) + *limit = li.limit; + + return (e.error); +} diff --git a/lib/libpfctl/libpfctl.h b/lib/libpfctl/libpfctl.h index 6d59d66a924a..44c7fa47e88d 100644 --- a/lib/libpfctl/libpfctl.h +++ b/lib/libpfctl/libpfctl.h @@ -495,5 +495,7 @@ int pfctl_natlook(struct pfctl_handle *h, int pfctl_set_debug(struct pfctl_handle *h, uint32_t level); int pfctl_set_timeout(struct pfctl_handle *h, uint32_t timeout, uint32_t seconds); int pfctl_get_timeout(struct pfctl_handle *h, uint32_t timeout, uint32_t *seconds); +int pfctl_set_limit(struct pfctl_handle *h, const int index, const uint limit); +int pfctl_get_limit(struct pfctl_handle *h, const int index, uint *limit); #endif diff --git a/sbin/pfctl/parse.y b/sbin/pfctl/parse.y index 2876eb6e89dc..d07a3fdc188e 100644 --- a/sbin/pfctl/parse.y +++ b/sbin/pfctl/parse.y @@ -5198,7 +5198,7 @@ limit_spec : STRING NUMBER yyerror("only positive values permitted"); YYERROR; } - if (pfctl_set_limit(pf, $1, $2) != 0) { + if (pfctl_apply_limit(pf, $1, $2) != 0) { yyerror("unable to set limit %s %u", $1, $2); free($1); YYERROR; diff --git a/sbin/pfctl/pfctl.c b/sbin/pfctl/pfctl.c index d97043fc5c66..ec718f6b0a99 100644 --- a/sbin/pfctl/pfctl.c +++ b/sbin/pfctl/pfctl.c @@ -1679,21 +1679,19 @@ pfctl_show_timeouts(int dev, int opts) int pfctl_show_limits(int dev, int opts) { - struct pfioc_limit pl; + unsigned int limit; int i; if (opts & PF_OPT_SHOWALL) pfctl_print_title("LIMITS:"); - memset(&pl, 0, sizeof(pl)); for (i = 0; pf_limits[i].name; i++) { - pl.index = pf_limits[i].index; - if (ioctl(dev, DIOCGETLIMIT, &pl)) + if (pfctl_get_limit(pfh, pf_limits[i].index, &limit)) err(1, "DIOCGETLIMIT"); printf("%-13s ", pf_limits[i].name); - if (pl.limit == UINT_MAX) + if (limit == UINT_MAX) printf("unlimited\n"); else - printf("hard limit %8u\n", pl.limit); + printf("hard limit %8u\n", limit); } return (0); } @@ -2425,7 +2423,7 @@ pfctl_load_options(struct pfctl *pf) } int -pfctl_set_limit(struct pfctl *pf, const char *opt, unsigned int limit) +pfctl_apply_limit(struct pfctl *pf, const char *opt, unsigned int limit) { int i; @@ -2451,12 +2449,7 @@ pfctl_set_limit(struct pfctl *pf, const char *opt, unsigned int limit) int pfctl_load_limit(struct pfctl *pf, unsigned int index, unsigned int limit) { - struct pfioc_limit pl; - - memset(&pl, 0, sizeof(pl)); - pl.index = index; - pl.limit = limit; - if (ioctl(pf->dev, DIOCSETLIMIT, &pl)) { + if (pfctl_set_limit(pf->h, index, limit)) { if (errno == EBUSY) warnx("Current pool size exceeds requested hard limit"); else diff --git a/sbin/pfctl/pfctl_parser.h b/sbin/pfctl/pfctl_parser.h index 06ab5d052631..6de998b34e52 100644 --- a/sbin/pfctl/pfctl_parser.h +++ b/sbin/pfctl/pfctl_parser.h @@ -288,7 +288,7 @@ void pfctl_clear_pool(struct pfctl_pool *); int pfctl_apply_timeout(struct pfctl *, const char *, int, int); int pfctl_set_reassembly(struct pfctl *, int, int); int pfctl_set_optimization(struct pfctl *, const char *); -int pfctl_set_limit(struct pfctl *, const char *, unsigned int); +int pfctl_apply_limit(struct pfctl *, const char *, unsigned int); int pfctl_set_logif(struct pfctl *, char *); int pfctl_set_hostid(struct pfctl *, u_int32_t); int pfctl_do_set_debug(struct pfctl *, char *); diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h index 16d3d90f4862..eb36c0aee4d1 100644 --- a/sys/net/pfvar.h +++ b/sys/net/pfvar.h @@ -2505,6 +2505,8 @@ int pf_ioctl_addrule(struct pf_krule *, uint32_t, void pf_ioctl_clear_status(void); int pf_ioctl_get_timeout(int, int *); int pf_ioctl_set_timeout(int, int, int *); +int pf_ioctl_get_limit(int, unsigned int *); +int pf_ioctl_set_limit(int, unsigned int, unsigned int *); void pf_krule_free(struct pf_krule *); void pf_krule_clear_counters(struct pf_krule *); diff --git a/sys/netpfil/pf/pf_ioctl.c b/sys/netpfil/pf/pf_ioctl.c index cef50c00283b..af3f914982ec 100644 --- a/sys/netpfil/pf/pf_ioctl.c +++ b/sys/netpfil/pf/pf_ioctl.c @@ -2483,6 +2483,40 @@ pf_ioctl_get_timeout(int timeout, int *seconds) return (0); } +int +pf_ioctl_set_limit(int index, unsigned int limit, unsigned int *old_limit) +{ + + PF_RULES_WLOCK(); + if (index < 0 || index >= PF_LIMIT_MAX || + V_pf_limits[index].zone == NULL) { + PF_RULES_WUNLOCK(); + return (EINVAL); + } + uma_zone_set_max(V_pf_limits[index].zone, limit); + if (old_limit != NULL) + *old_limit = V_pf_limits[index].limit; + V_pf_limits[index].limit = limit; + PF_RULES_WUNLOCK(); + + return (0); +} + +int +pf_ioctl_get_limit(int index, unsigned int *limit) +{ + PF_RULES_RLOCK_TRACKER; + + if (index < 0 || index >= PF_LIMIT_MAX) + return (EINVAL); + + PF_RULES_RLOCK(); + *limit = V_pf_limits[index].limit; + PF_RULES_RUNLOCK(); + + return (0); +} + static int pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td) { @@ -3894,32 +3928,16 @@ DIOCGETSTATESV2_full: case DIOCGETLIMIT: { struct pfioc_limit *pl = (struct pfioc_limit *)addr; - if (pl->index < 0 || pl->index >= PF_LIMIT_MAX) { - error = EINVAL; - break; - } - PF_RULES_RLOCK(); - pl->limit = V_pf_limits[pl->index].limit; - PF_RULES_RUNLOCK(); + error = pf_ioctl_get_limit(pl->index, &pl->limit); break; } case DIOCSETLIMIT: { struct pfioc_limit *pl = (struct pfioc_limit *)addr; - int old_limit; + unsigned int old_limit; - PF_RULES_WLOCK(); - if (pl->index < 0 || pl->index >= PF_LIMIT_MAX || - V_pf_limits[pl->index].zone == NULL) { - PF_RULES_WUNLOCK(); - error = EINVAL; - break; - } - uma_zone_set_max(V_pf_limits[pl->index].zone, pl->limit); - old_limit = V_pf_limits[pl->index].limit; - V_pf_limits[pl->index].limit = pl->limit; + error = pf_ioctl_set_limit(pl->index, pl->limit, &old_limit); pl->limit = old_limit; - PF_RULES_WUNLOCK(); break; } diff --git a/sys/netpfil/pf/pf_nl.c b/sys/netpfil/pf/pf_nl.c index 026f8caab535..57b1a4a6b5b3 100644 --- a/sys/netpfil/pf/pf_nl.c +++ b/sys/netpfil/pf/pf_nl.c @@ -1410,6 +1410,67 @@ pf_handle_get_timeout(struct nlmsghdr *hdr, struct nl_pstate *npt) return (0); } +struct pf_nl_set_limit +{ + uint32_t index; + uint32_t limit; +}; +#define _OUT(_field) offsetof(struct pf_nl_set_limit, _field) +static const struct nlattr_parser nla_p_set_limit[] = { + { .type = PF_LI_INDEX, .off = _OUT(index), .cb = nlattr_get_uint32 }, + { .type = PF_LI_LIMIT, .off = _OUT(limit), .cb = nlattr_get_uint32 }, +}; +static const struct nlfield_parser nlf_p_set_limit[] = {}; +#undef _OUT +NL_DECLARE_PARSER(set_limit_parser, struct genlmsghdr, nlf_p_set_limit, nla_p_set_limit); + +static int +pf_handle_set_limit(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct pf_nl_set_limit attrs = {}; + int error; + + error = nl_parse_nlmsg(hdr, &set_limit_parser, npt, &attrs); + if (error != 0) + return (error); + + return (pf_ioctl_set_limit(attrs.index, attrs.limit, NULL)); +} + +static int +pf_handle_get_limit(struct nlmsghdr *hdr, struct nl_pstate *npt) +{ + struct pf_nl_set_limit attrs = {}; + struct nl_writer *nw = npt->nw; + struct genlmsghdr *ghdr_new; + int error; + + error = nl_parse_nlmsg(hdr, &set_limit_parser, npt, &attrs); + if (error != 0) + return (error); + + error = pf_ioctl_get_limit(attrs.index, &attrs.limit); + 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_GET_LIMIT; + ghdr_new->version = 0; + ghdr_new->reserved = 0; + + nlattr_add_u32(nw, PF_LI_LIMIT, attrs.limit); + + if (!nlmsg_end(nw)) { + nlmsg_abort(nw); + return (ENOMEM); + } + + return (0); +} + static const struct nlhdr_parser *all_parsers[] = { &state_parser, &addrule_parser, @@ -1419,6 +1480,7 @@ static const struct nlhdr_parser *all_parsers[] = { &natlook_parser, &set_debug_parser, &set_timeout_parser, + &set_limit_parser, }; static int family_id; @@ -1536,6 +1598,20 @@ static const struct genl_cmd pf_cmds[] = { .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, .cmd_priv = PRIV_NETINET_PF, }, + { + .cmd_num = PFNL_CMD_SET_LIMIT, + .cmd_name = "SET_LIMIT", + .cmd_cb = pf_handle_set_limit, + .cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NETINET_PF, + }, + { + .cmd_num = PFNL_CMD_GET_LIMIT, + .cmd_name = "GET_LIMIT", + .cmd_cb = pf_handle_get_limit, + .cmd_flags = GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL, + .cmd_priv = PRIV_NETINET_PF, + }, }; void diff --git a/sys/netpfil/pf/pf_nl.h b/sys/netpfil/pf/pf_nl.h index 5f9d8166ca50..437cc6482dbb 100644 --- a/sys/netpfil/pf/pf_nl.h +++ b/sys/netpfil/pf/pf_nl.h @@ -52,6 +52,8 @@ enum { PFNL_CMD_SET_DEBUG = 14, PFNL_CMD_SET_TIMEOUT = 15, PFNL_CMD_GET_TIMEOUT = 16, + PFNL_CMD_SET_LIMIT = 17, + PFNL_CMD_GET_LIMIT = 18, __PFNL_CMD_MAX, }; #define PFNL_CMD_MAX (__PFNL_CMD_MAX -1) @@ -342,6 +344,12 @@ enum pf_timeout_types_t { PF_TO_SECONDS = 2, /* u32 */ }; +enum pf_limit_types_t { + PF_LI_UNSPEC, + PF_LI_INDEX = 1, /* u32 */ + PF_LI_LIMIT = 2, /* u32 */ +}; + #ifdef _KERNEL void pf_nl_register(void); diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile index 089db2f19766..2840dc92f2fa 100644 --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -17,6 +17,7 @@ ATF_TESTS_SH+= altq \ fragmentation_no_reassembly \ get_state \ icmp \ + limits \ loginterface \ killstate \ macro \ diff --git a/tests/sys/netpfil/pf/limits.sh b/tests/sys/netpfil/pf/limits.sh new file mode 100644 index 000000000000..474684bef660 --- /dev/null +++ b/tests/sys/netpfil/pf/limits.sh @@ -0,0 +1,66 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2024 Kristof Provost <k...@freebsd.org> +# +# 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 'Test setting and retrieving limits' + atf_set require.user root +} + +basic_body() +{ + pft_init + + vnet_mkjail alcatraz + + pft_set_rules alcatraz \ + "set limit states 200" \ + "set limit frags 100" \ + "set limit src-nodes 50" \ + "set limit table-entries 25" + + atf_check -s exit:0 -o match:'states.*200' \ + jexec alcatraz pfctl -sm + atf_check -s exit:0 -o match:'frags.*100' \ + jexec alcatraz pfctl -sm + atf_check -s exit:0 -o match:'src-nodes.*50' \ + jexec alcatraz pfctl -sm + atf_check -s exit:0 -o match:'table-entries.*25' \ + jexec alcatraz pfctl -sm +} + +basic_cleanup() +{ + pft_cleanup +} + +atf_init_test_cases() +{ + atf_add_test_case "basic" +} diff --git a/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c b/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c index f02329901f69..9f7e6d4f31cb 100644 --- a/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c +++ b/usr.sbin/bsnmpd/modules/snmp_pf/pf_snmp.c @@ -318,36 +318,34 @@ pf_limits(struct snmp_context __unused *ctx, struct snmp_value *val, u_int sub, u_int __unused vindex, enum snmp_op op) { asn_subid_t which = val->var.subs[sub - 1]; - struct pfioc_limit pl; + unsigned int index, limit; if (op == SNMP_OP_SET) return (SNMP_ERR_NOT_WRITEABLE); if (op == SNMP_OP_GET) { - bzero(&pl, sizeof(struct pfioc_limit)); - switch (which) { case LEAF_pfLimitsStates: - pl.index = PF_LIMIT_STATES; + index = PF_LIMIT_STATES; break; case LEAF_pfLimitsSrcNodes: - pl.index = PF_LIMIT_SRC_NODES; + index = PF_LIMIT_SRC_NODES; break; case LEAF_pfLimitsFrags: - pl.index = PF_LIMIT_FRAGS; + index = PF_LIMIT_FRAGS; break; default: return (SNMP_ERR_NOSUCHNAME); } - if (ioctl(pfctl_fd(pfh), DIOCGETLIMIT, &pl)) { + if (pfctl_get_limit(pfh, index, &limit)) { syslog(LOG_ERR, "pf_limits(): ioctl(): %s", strerror(errno)); return (SNMP_ERR_GENERR); } - val->v.uint32 = pl.limit; + val->v.uint32 = limit; return (SNMP_ERR_NOERROR); }