pf: add a way to list creator ids

Allow userspace to retrieve a list of distinct creator ids for the
current states.

This is used by pfSense, and used to require dumping all states to
userspace. It's rather inefficient to export a (potentially extremely
large) state table to obtain a handful (typically 2) of 32-bit integers.

Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D42092
This commit is contained in:
Kristof Provost 2023-10-04 12:27:54 +02:00
parent f218b851da
commit a7191e5d7b
7 changed files with 178 additions and 2 deletions

View file

@ -1106,6 +1106,71 @@ pfctl_set_keepcounters(int dev, bool keep)
return (ret);
}
struct pfctl_creator {
uint32_t id;
};
#define _IN(_field) offsetof(struct genlmsghdr, _field)
#define _OUT(_field) offsetof(struct pfctl_creator, _field)
static struct snl_attr_parser ap_creators[] = {
{ .type = PF_ST_CREATORID, .off = _OUT(id), .cb = snl_attr_get_uint32 },
};
static struct snl_field_parser fp_creators[] = {
};
#undef _IN
#undef _OUT
SNL_DECLARE_PARSER(creator_parser, struct genlmsghdr, fp_creators, ap_creators);
static int
pfctl_get_creators_nl(struct snl_state *ss, uint32_t *creators, size_t *len)
{
int family_id = snl_get_genl_family(ss, PFNL_FAMILY_NAME);
size_t i = 0;
struct nlmsghdr *hdr;
struct snl_writer nw;
snl_init_writer(ss, &nw);
hdr = snl_create_genl_msg_request(&nw, family_id, PFNL_CMD_GETCREATORS);
hdr->nlmsg_flags |= NLM_F_DUMP;
snl_finalize_msg(&nw);
uint32_t seq_id = hdr->nlmsg_seq;
snl_send_message(ss, hdr);
struct snl_errmsg_data e = {};
while ((hdr = snl_read_reply_multi(ss, seq_id, &e)) != NULL) {
struct pfctl_creator c;
bzero(&c, sizeof(c));
if (!snl_parse_nlmsg(ss, hdr, &creator_parser, &c))
continue;
creators[i] = c.id;
i++;
if (i > *len)
return (E2BIG);
}
*len = i;
return (0);
}
int
pfctl_get_creatorids(uint32_t *creators, size_t *len)
{
struct snl_state ss = {};
int error;
snl_init(&ss, NETLINK_GENERIC);
error = pfctl_get_creators_nl(&ss, creators, len);
snl_free(&ss);
return (error);
}
static void
pfctl_nv_add_state_cmp(nvlist_t *nvl, const char *name,
const struct pfctl_state_cmp *cmp)
@ -1199,7 +1264,8 @@ static struct snl_field_parser fp_state[] = {
SNL_DECLARE_PARSER(state_parser, struct genlmsghdr, fp_state, ap_state);
static const struct snl_hdr_parser *all_parsers[] = {
&state_parser, &skey_parser, &speer_parser
&state_parser, &skey_parser, &speer_parser,
&creator_parser,
};
static int

View file

@ -413,6 +413,7 @@ int pfctl_add_rule(int dev, const struct pfctl_rule *r,
const char *anchor, const char *anchor_call, uint32_t ticket,
uint32_t pool_ticket);
int pfctl_set_keepcounters(int dev, bool keep);
int pfctl_get_creatorids(uint32_t *creators, size_t *len);
typedef int (*pfctl_get_state_fn)(struct pfctl_state *, void *);
int pfctl_get_states_iter(pfctl_get_state_fn f, void *arg);
int pfctl_get_states(int dev, struct pfctl_states *states);

View file

@ -233,7 +233,7 @@ static const char * const clearopt_list[] = {
static const char * const showopt_list[] = {
"ether", "nat", "queue", "rules", "Anchors", "Sources", "states",
"info", "Interfaces", "labels", "timeouts", "memory", "Tables",
"osfp", "Running", "all", NULL
"osfp", "Running", "all", "creatorids", NULL
};
static const char * const tblcmdopt_list[] = {
@ -1639,6 +1639,22 @@ pfctl_show_limits(int dev, int opts)
return (0);
}
void
pfctl_show_creators(int opts)
{
int ret;
uint32_t creators[16];
size_t count = nitems(creators);
ret = pfctl_get_creatorids(creators, &count);
if (ret != 0)
errx(ret, "Failed to retrieve creators");
printf("Creator IDs:\n");
for (size_t i = 0; i < count; i++)
printf("%08x\n", creators[i]);
}
/* callbacks for rule/nat/rdr/addr */
int
pfctl_add_pool(struct pfctl *pf, struct pfctl_pool *p, sa_family_t af)
@ -3121,6 +3137,9 @@ main(int argc, char *argv[])
case 'I':
pfctl_show_ifaces(ifaceopt, opts);
break;
case 'c':
pfctl_show_creators(opts);
break;
}
}

View file

@ -88,6 +88,7 @@ int pfctl_command_tables(int, char *[], char *, const char *, char *,
int pfctl_show_altq(int, const char *, int, int);
void warn_namespace_collision(const char *);
int pfctl_show_ifaces(const char *, int);
void pfctl_show_creators(int);
FILE *pfctl_fopen(const char *, const char *);
#ifdef __FreeBSD__

View file

@ -246,6 +246,30 @@ handle_getstate(struct nlpcb *nlp, struct nl_parsed_state *attrs,
return (dump_state(nlp, hdr, s, npt));
}
static int
dump_creatorid(struct nlpcb *nlp, const struct nlmsghdr *hdr, uint32_t creator,
struct nl_pstate *npt)
{
struct nl_writer *nw = npt->nw;
if (!nlmsg_reply(nw, hdr, sizeof(struct genlmsghdr)))
goto enomem;
struct genlmsghdr *ghdr_new = nlmsg_reserve_object(nw, struct genlmsghdr);
ghdr_new->cmd = PFNL_CMD_GETCREATORS;
ghdr_new->version = 0;
ghdr_new->reserved = 0;
nlattr_add_u32(nw, PF_ST_CREATORID, htonl(creator));
if (nlmsg_end(nw))
return (0);
enomem:
nlmsg_abort(nw);
return (ENOMEM);
}
static int
pf_handle_getstates(struct nlmsghdr *hdr, struct nl_pstate *npt)
{
@ -264,6 +288,56 @@ pf_handle_getstates(struct nlmsghdr *hdr, struct nl_pstate *npt)
return (error);
}
static int
pf_handle_getcreators(struct nlmsghdr *hdr, struct nl_pstate *npt)
{
uint32_t creators[16];
int error = 0;
bzero(creators, sizeof(creators));
for (int i = 0; i < pf_hashmask; i++) {
struct pf_idhash *ih = &V_pf_idhash[i];
struct pf_kstate *s;
if (LIST_EMPTY(&ih->states))
continue;
PF_HASHROW_LOCK(ih);
LIST_FOREACH(s, &ih->states, entry) {
int j;
if (s->timeout == PFTM_UNLINKED)
continue;
for (j = 0; j < nitems(creators); j++) {
if (creators[j] == s->creatorid)
break;
if (creators[j] == 0) {
creators[j] = s->creatorid;
break;
}
}
if (j == nitems(creators))
printf("Warning: too many creators!\n");
}
PF_HASHROW_UNLOCK(ih);
}
hdr->nlmsg_flags |= NLM_F_MULTI;
for (int i = 0; i < nitems(creators); i++) {
if (creators[i] == 0)
break;
error = dump_creatorid(npt->nlp, hdr, creators[i], npt);
}
if (!nlmsg_end_dump(npt->nw, error, hdr)) {
NL_LOG(LOG_DEBUG, "Unable to finalize the dump");
return (ENOMEM);
}
return (error);
}
static const struct nlhdr_parser *all_parsers[] = { &state_parser };
static int family_id;
@ -275,6 +349,12 @@ static const struct genl_cmd pf_cmds[] = {
.cmd_cb = pf_handle_getstates,
.cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL,
},
{
.cmd_num = PFNL_CMD_GETCREATORS,
.cmd_name = "GETCREATORS",
.cmd_cb = pf_handle_getcreators,
.cmd_flags = GENL_CMD_CAP_DO | GENL_CMD_CAP_DUMP | GENL_CMD_CAP_HASPOL,
},
};
void

View file

@ -37,6 +37,7 @@
enum {
PFNL_CMD_UNSPEC = 0,
PFNL_CMD_GETSTATES = 1,
PFNL_CMD_GETCREATORS = 2,
__PFNL_CMD_MAX,
};
#define PFNL_CMD_MAX (__PFNL_CMD_MAX -1)

View file

@ -78,6 +78,8 @@ common_body()
"set skip on ${epair_sync}b" \
"pass out keep state"
hostid_one=$(jexec one pfctl -si -v | awk '/Hostid:/ { gsub(/0x/, "", $2); printf($2); }')
ifconfig ${epair_one}b 198.51.100.254/24 up
ping -c 1 -S 198.51.100.254 198.51.100.1
@ -89,6 +91,12 @@ common_body()
grep 198.51.100.254 ; then
atf_fail "state not found on synced host"
fi
if ! jexec two pfctl -sc | grep ""${hostid_one}"";
then
jexec two pfctl -sc
atf_fail "HostID for host one not found on two"
fi
}
basic_cleanup()