pf: migrate DIOCGETLIMIT/DIOCSETLIMIT to netlink

Event:		Kitchener-Waterloo Hackathon 202406
This commit is contained in:
Kristof Provost 2024-06-07 03:08:50 +02:00
parent ea6d6addc9
commit d9ab899931
12 changed files with 291 additions and 45 deletions

View file

@ -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);
}

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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 *);

View file

@ -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 *);

View file

@ -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 @@ pfioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flags, struct thread *td
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;
}

View file

@ -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

View file

@ -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);

View file

@ -17,6 +17,7 @@ ATF_TESTS_SH+= altq \
fragmentation_no_reassembly \
get_state \
icmp \
limits \
loginterface \
killstate \
macro \

View file

@ -0,0 +1,66 @@
#
# SPDX-License-Identifier: BSD-2-Clause
#
# Copyright (c) 2024 Kristof Provost <kp@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"
}

View file

@ -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);
}