netlink: refactor KPI for generic Netlink modules

Now that the family and group are completely private to netlink_generic.c,
provide a simple and robust KPI, that would require very simple guarantees
from both KPI and the module:

* Strings are used only for family and group registration, that return ID:
  uint16_t genl_register_family(const char *name, ...
  uint32_t genl_register_group(uint16_t family, const char *name, ...
* Once created families and groups are guaranteed to not disappear and
  be addressable by their ID.
* All subsequent calls, including deregistration shall use ID.

Reviewed by:		kp
Differential Revision:	https://reviews.freebsd.org/D48845
This commit is contained in:
Gleb Smirnoff 2025-02-05 10:09:06 -08:00
parent 194bb58b80
commit ee507b70f1
9 changed files with 125 additions and 196 deletions

View file

@ -2969,26 +2969,25 @@ static const struct genl_cmd carp_cmds[] = {
},
};
static uint16_t carp_family_id;
static void
carp_nl_register(void)
{
bool ret __diagused;
int family_id __diagused;
NL_VERIFY_PARSERS(all_parsers);
family_id = genl_register_family(CARP_NL_FAMILY_NAME, 0, 2,
carp_family_id = genl_register_family(CARP_NL_FAMILY_NAME, 0, 2,
CARP_NL_CMD_MAX);
MPASS(family_id != 0);
MPASS(carp_family_id != 0);
ret = genl_register_cmds(CARP_NL_FAMILY_NAME, carp_cmds,
nitems(carp_cmds));
ret = genl_register_cmds(carp_family_id, carp_cmds, nitems(carp_cmds));
MPASS(ret);
}
static void
carp_nl_unregister(void)
{
genl_unregister_family(CARP_NL_FAMILY_NAME);
genl_unregister_family(carp_family_id);
}
static void

View file

@ -93,16 +93,13 @@ struct genl_cmd {
uint16_t genl_register_family(const char *family_name, size_t hdrsize,
uint16_t family_version, uint16_t max_attr_idx);
bool genl_unregister_family(const char *family_name);
bool genl_register_cmds(const char *family_name, const struct genl_cmd *cmds,
int count);
uint32_t genl_register_group(const char *family_name, const char *group_name);
void genl_unregister_family(uint16_t family);
bool genl_register_cmds(uint16_t family, const struct genl_cmd *cmds,
u_int count);
uint32_t genl_register_group(uint16_t family, const char *group_name);
struct genl_family;
const char *genl_get_family_name(const struct genl_family *gf);
uint16_t genl_get_family_id(const struct genl_family *gf);
typedef void (*genl_family_event_handler_t)(void *arg, const struct genl_family *gf, int action);
typedef void (*genl_family_event_handler_t)(void *arg, const char *family_name,
uint16_t family_id, u_int action);
EVENTHANDLER_DECLARE(genl_family_event, genl_family_event_handler_t);
struct thread;

View file

@ -89,6 +89,24 @@ static struct genl_group {
},
};
static inline struct genl_family *
genl_family(uint16_t family_id)
{
struct genl_family *gf;
gf = &families[family_id - GENL_MIN_ID];
KASSERT(family_id - GENL_MIN_ID < MAX_FAMILIES &&
gf->family_name != NULL, ("family %u does not exist", family_id));
return (gf);
}
static inline uint16_t
genl_family_id(const struct genl_family *gf)
{
MPASS(gf >= &families[0] && gf < &families[MAX_FAMILIES]);
return ((uint16_t)(gf - &families[0]) + GENL_MIN_ID);
}
/*
* Handler called by netlink subsystem when matching netlink message is received
*/
@ -96,22 +114,26 @@ static int
genl_handle_message(struct nlmsghdr *hdr, struct nl_pstate *npt)
{
struct nlpcb *nlp = npt->nlp;
struct genl_family *gf = NULL;
struct genl_family *gf;
uint16_t family_id;
int error = 0;
int family_id = (int)hdr->nlmsg_type - GENL_MIN_ID;
if (__predict_false(family_id < 0 || (gf = genl_get_family(family_id)) == NULL)) {
NLP_LOG(LOG_DEBUG, nlp, "invalid message type: %d", hdr->nlmsg_type);
return (ENOTSUP);
}
if (__predict_false(hdr->nlmsg_len < sizeof(struct nlmsghdr) +
GENL_HDRLEN)) {
NLP_LOG(LOG_DEBUG, nlp, "invalid message size: %d", hdr->nlmsg_len);
NLP_LOG(LOG_DEBUG, nlp, "invalid message size: %d",
hdr->nlmsg_len);
return (EINVAL);
}
family_id = hdr->nlmsg_type - GENL_MIN_ID;
gf = &families[family_id];
if (__predict_false(family_id >= MAX_FAMILIES ||
gf->family_name == NULL)) {
NLP_LOG(LOG_DEBUG, nlp, "invalid message type: %d",
hdr->nlmsg_type);
return (ENOTSUP);
}
struct genlmsghdr *ghdr = (struct genlmsghdr *)(hdr + 1);
if (ghdr->cmd >= gf->family_cmd_size || gf->family_cmds[ghdr->cmd].cmd_cb == NULL) {
@ -158,7 +180,7 @@ dump_family(struct nlmsghdr *hdr, struct genlmsghdr *ghdr,
ghdr_new->reserved = 0;
nlattr_add_string(nw, CTRL_ATTR_FAMILY_NAME, gf->family_name);
nlattr_add_u16(nw, CTRL_ATTR_FAMILY_ID, genl_get_family_id(gf));
nlattr_add_u16(nw, CTRL_ATTR_FAMILY_ID, genl_family_id(gf));
nlattr_add_u32(nw, CTRL_ATTR_VERSION, gf->family_version);
nlattr_add_u32(nw, CTRL_ATTR_HDRSIZE, gf->family_hdrsize);
nlattr_add_u32(nw, CTRL_ATTR_MAXATTR, gf->family_attr_max);
@ -185,9 +207,10 @@ dump_family(struct nlmsghdr *hdr, struct genlmsghdr *ghdr,
int off = nlattr_add_nested(nw, CTRL_ATTR_MCAST_GROUPS);
if (off == 0)
goto enomem;
for (int i = 0, cnt = 0; i < MAX_GROUPS; i++) {
struct genl_group *gg = genl_get_group(i);
if (gg == NULL || gg->group_family != gf)
for (u_int i = 0, cnt = 0; i < MAX_GROUPS; i++) {
struct genl_group *gg = &groups[i];
if (gg->group_family != gf)
continue;
int cmd_off = nlattr_add_nested(nw, ++cnt);
@ -207,14 +230,9 @@ enomem:
return (ENOMEM);
}
/* Declare ourself as a user */
static void nlctrl_notify(void *arg, const struct genl_family *gf, int action);
static eventhandler_tag family_event_tag;
struct nl_parsed_family {
uint32_t family_id;
char *family_name;
uint16_t family_id;
uint8_t version;
};
@ -232,18 +250,6 @@ static struct nlattr_parser nla_p_generic[] = {
#undef _OUT
NL_DECLARE_PARSER(genl_parser, struct genlmsghdr, nlf_p_generic, nla_p_generic);
static bool
match_family(const struct genl_family *gf, const struct nl_parsed_family *attrs)
{
if (gf->family_name == NULL)
return (false);
if (attrs->family_id != 0 && attrs->family_id != genl_get_family_id(gf))
return (false);
if (attrs->family_name != NULL && strcmp(attrs->family_name, gf->family_name))
return (false);
return (true);
}
static int
nlctrl_handle_getfamily(struct nlmsghdr *hdr, struct nl_pstate *npt)
{
@ -259,21 +265,27 @@ nlctrl_handle_getfamily(struct nlmsghdr *hdr, struct nl_pstate *npt)
};
if (attrs.family_id != 0 || attrs.family_name != NULL) {
/* Resolve request */
for (int i = 0; i < MAX_FAMILIES; i++) {
struct genl_family *gf = genl_get_family(i);
if (gf != NULL && match_family(gf, &attrs)) {
error = dump_family(hdr, &ghdr, gf, npt->nw);
return (error);
}
for (u_int i = 0; i < MAX_FAMILIES; i++) {
struct genl_family *gf = &families[i];
if (gf->family_name == NULL)
continue;
if (attrs.family_id != 0 &&
attrs.family_id != genl_family_id(gf))
continue;
if (attrs.family_name != NULL &&
strcmp(attrs.family_name, gf->family_name) != 0)
continue;
return (dump_family(hdr, &ghdr, gf, npt->nw));
}
return (ENOENT);
}
hdr->nlmsg_flags = hdr->nlmsg_flags | NLM_F_MULTI;
for (int i = 0; i < MAX_FAMILIES; i++) {
struct genl_family *gf = genl_get_family(i);
if (gf != NULL && match_family(gf, &attrs)) {
for (u_int i = 0; i < MAX_FAMILIES; i++) {
struct genl_family *gf = &families[i];
if (gf->family_name != NULL) {
error = dump_family(hdr, &ghdr, gf, npt->nw);
if (error != 0)
break;
@ -289,12 +301,15 @@ nlctrl_handle_getfamily(struct nlmsghdr *hdr, struct nl_pstate *npt)
}
static void
nlctrl_notify(void *arg __unused, const struct genl_family *gf, int cmd)
nlctrl_notify(void *arg __unused, const char *family_name __unused,
uint16_t family_id, u_int cmd)
{
struct nlmsghdr hdr = {.nlmsg_type = NETLINK_GENERIC };
struct genlmsghdr ghdr = { .cmd = cmd };
struct genl_family *gf;
struct nl_writer nw;
gf = genl_family(family_id);
if (!nl_writer_group(&nw, NLMSG_SMALL, NETLINK_GENERIC, CTRL_GROUP_ID,
0, false)) {
NL_LOG(LOG_DEBUG, "error allocating group writer");
@ -306,6 +321,7 @@ nlctrl_notify(void *arg __unused, const struct genl_family *gf, int cmd)
}
static const struct nlhdr_parser *all_parsers[] = { &genl_parser };
static eventhandler_tag family_event_tag;
static void
genl_load_all(void *u __unused)
@ -338,30 +354,6 @@ SX_SYSINIT(genl_lock, &sx_lock, "genetlink lock");
#define GENL_ASSERT_LOCKED() sx_assert(&sx_lock, SA_LOCKED)
#define GENL_ASSERT_XLOCKED() sx_assert(&sx_lock, SA_XLOCKED)
static struct genl_family *
find_family(const char *family_name)
{
GENL_ASSERT_LOCKED();
for (u_int i = 0; i < MAX_FAMILIES; i++)
if (families[i].family_name != NULL &&
strcmp(families[i].family_name, family_name) == 0)
return (&families[i]);
return (NULL);
}
static struct genl_family *
find_empty_family_id(const char *family_name)
{
GENL_ASSERT_LOCKED();
/* Microoptimization: index 0 is reserved for the control family */
for (u_int i = 1; i < MAX_FAMILIES; i++)
if (families[i].family_name == NULL)
return (&families[i]);
return (NULL);
}
uint16_t
genl_register_family(const char *family_name, size_t hdrsize,
uint16_t family_version, uint16_t max_attr_idx)
@ -369,13 +361,21 @@ genl_register_family(const char *family_name, size_t hdrsize,
struct genl_family *gf;
uint16_t family_id;
GENL_LOCK();
if (find_family(family_name) != NULL) {
GENL_UNLOCK();
return (0);
}
MPASS(family_name != NULL);
gf = find_empty_family_id(family_name);
GENL_LOCK();
for (u_int i = 0; i < MAX_FAMILIES; i++)
if (families[i].family_name != NULL &&
strcmp(families[i].family_name, family_name) == 0)
return (0);
/* Microoptimization: index 0 is reserved for the control family. */
gf = NULL;
for (u_int i = 1; i < MAX_FAMILIES; i++)
if (families[i].family_name == NULL) {
gf = &families[i];
break;
}
KASSERT(gf, ("%s: maximum of %u generic netlink families allocated",
__func__, MAX_FAMILIES));
@ -385,30 +385,27 @@ genl_register_family(const char *family_name, size_t hdrsize,
.family_hdrsize = hdrsize,
.family_attr_max = max_attr_idx,
};
family_id = genl_get_family_id(gf);
family_id = genl_family_id(gf);
GENL_UNLOCK();
NL_LOG(LOG_DEBUG2, "Registered family %s id %d", gf->family_name,
family_id);
EVENTHANDLER_INVOKE(genl_family_event, gf, CTRL_CMD_NEWFAMILY);
EVENTHANDLER_INVOKE(genl_family_event, gf->family_name, family_id,
CTRL_CMD_NEWFAMILY);
return (family_id);
}
static void
free_family(struct genl_family *gf)
void
genl_unregister_family(uint16_t family_id)
{
if (gf->family_cmds != NULL)
free(gf->family_cmds, M_NETLINK);
}
struct genl_family *gf;
/*
* unregister groups of a given family
*/
static void
unregister_groups(const struct genl_family *gf)
{
GENL_LOCK();
gf = genl_family(family_id);
EVENTHANDLER_INVOKE(genl_family_event, gf->family_name,
family_id, CTRL_CMD_DELFAMILY);
for (u_int i = 0; i < MAX_GROUPS; i++) {
struct genl_group *gg = &groups[i];
if (gg->group_family == gf && gg->group_name != NULL) {
@ -416,44 +413,21 @@ unregister_groups(const struct genl_family *gf)
gg->group_name = NULL;
}
}
}
/*
* Can sleep, I guess
*/
bool
genl_unregister_family(const char *family_name)
{
bool found = false;
GENL_LOCK();
struct genl_family *gf = find_family(family_name);
if (gf != NULL) {
EVENTHANDLER_INVOKE(genl_family_event, gf, CTRL_CMD_DELFAMILY);
found = true;
unregister_groups(gf);
/* TODO: zero pointer first */
free_family(gf);
bzero(gf, sizeof(*gf));
}
if (gf->family_cmds != NULL)
free(gf->family_cmds, M_NETLINK);
bzero(gf, sizeof(*gf));
GENL_UNLOCK();
return (found);
}
bool
genl_register_cmds(const char *family_name, const struct genl_cmd *cmds,
int count)
genl_register_cmds(uint16_t family_id, const struct genl_cmd *cmds,
u_int count)
{
struct genl_family *gf;
uint16_t cmd_size;
GENL_LOCK();
if ((gf = find_family(family_name)) == NULL) {
GENL_UNLOCK();
return (false);
}
gf = genl_family(family_id);
cmd_size = gf->family_cmd_size;
@ -490,33 +464,23 @@ genl_register_cmds(const char *family_name, const struct genl_cmd *cmds,
return (true);
}
static struct genl_group *
find_group(const struct genl_family *gf, const char *group_name)
{
for (u_int i = 0; i < MAX_GROUPS; i++) {
struct genl_group *gg = &groups[i];
if (gg->group_family == gf &&
!strcmp(gg->group_name, group_name))
return (gg);
}
return (NULL);
}
uint32_t
genl_register_group(const char *family_name, const char *group_name)
genl_register_group(uint16_t family_id, const char *group_name)
{
struct genl_family *gf;
uint32_t group_id = 0;
MPASS(family_name != NULL);
MPASS(group_name != NULL);
GENL_LOCK();
if ((gf = find_family(family_name)) == NULL ||
find_group(gf, group_name) != NULL) {
GENL_UNLOCK();
return (0);
}
gf = genl_family(family_id);
for (u_int i = 0; i < MAX_GROUPS; i++)
if (groups[i].group_family == gf &&
strcmp(groups[i].group_name, group_name) == 0) {
GENL_UNLOCK();
return (0);
}
/* Microoptimization: index 0 is reserved for the control family */
for (u_int i = 1; i < MAX_GROUPS; i++) {
@ -533,29 +497,3 @@ genl_register_group(const char *family_name, const char *group_name)
return (group_id);
}
/* accessors */
struct genl_family *
genl_get_family(uint16_t family_id)
{
return ((family_id < MAX_FAMILIES) ? &families[family_id] : NULL);
}
const char *
genl_get_family_name(const struct genl_family *gf)
{
return (gf->family_name);
}
uint16_t
genl_get_family_id(const struct genl_family *gf)
{
MPASS(gf >= &families[0] && gf < &families[MAX_FAMILIES]);
return ((uint16_t)(gf - &families[0]) + GENL_MIN_ID);
}
struct genl_group *
genl_get_group(uint32_t group_id)
{
return ((group_id < MAX_GROUPS) ? &groups[group_id] : NULL);
}

View file

@ -45,7 +45,7 @@ _DECLARE_DEBUG(LOG_INFO);
MALLOC_DEFINE(M_NLSE, "nlsysevent", "Memory used for Netlink sysevent");
#define NLSE_FAMILY_NAME "nlsysevent"
static uint32_t ctrl_family_id;
static uint16_t ctrl_family_id;
#define MAX_SYSEVENT_GROUPS 64
static struct sysevent_group {
@ -118,7 +118,8 @@ sysevent_new_group(size_t index, const char *name)
return;
}
sysevent_groups[index].name = strdup(name, M_NLSE);
sysevent_groups[index].id = genl_register_group(NLSE_FAMILY_NAME, sysevent_groups[index].name);
sysevent_groups[index].id = genl_register_group(ctrl_family_id,
sysevent_groups[index].name);
}
static struct sysevent_group *
@ -171,7 +172,7 @@ static void
nlsysevent_unload(void)
{
devctl_unset_notify_hook();
genl_unregister_family(NLSE_FAMILY_NAME);
genl_unregister_family(ctrl_family_id);
for (size_t i = 0; i < MAX_SYSEVENT_GROUPS; i++) {
if (sysevent_groups[i].name == NULL)
break;

View file

@ -138,10 +138,6 @@ void nl_set_source_metadata(struct mbuf *m, int num_messages);
struct nl_buf *nl_buf_alloc(size_t len, int mflag);
void nl_buf_free(struct nl_buf *nb);
/* netlink_generic.c */
struct genl_family *genl_get_family(uint16_t family_id);
struct genl_group *genl_get_group(uint32_t group_id);
#define MAX_FAMILIES 20
#define MAX_GROUPS 64

View file

@ -1858,7 +1858,7 @@ static const struct nlhdr_parser *all_parsers[] = {
&table_parser,
};
static int family_id;
static uint16_t family_id;
static const struct genl_cmd pf_cmds[] = {
{
@ -2051,11 +2051,11 @@ pf_nl_register(void)
NL_VERIFY_PARSERS(all_parsers);
family_id = genl_register_family(PFNL_FAMILY_NAME, 0, 2, PFNL_CMD_MAX);
genl_register_cmds(PFNL_FAMILY_NAME, pf_cmds, nitems(pf_cmds));
genl_register_cmds(family_id, pf_cmds, nitems(pf_cmds));
}
void
pf_nl_unregister(void)
{
genl_unregister_family(PFNL_FAMILY_NAME);
genl_unregister_family(family_id);
}

View file

@ -1783,11 +1783,11 @@ static const struct nlhdr_parser *all_parsers[] = {
static unsigned pflow_do_osd_jail_slot;
static uint16_t family_id;
static int
pflow_init(void)
{
bool ret;
int family_id __diagused;
NL_VERIFY_PARSERS(all_parsers);
@ -1796,10 +1796,10 @@ pflow_init(void)
};
pflow_do_osd_jail_slot = osd_jail_register(NULL, methods);
family_id = genl_register_family(PFLOWNL_FAMILY_NAME, 0, 2, PFLOWNL_CMD_MAX);
family_id = genl_register_family(PFLOWNL_FAMILY_NAME, 0, 2,
PFLOWNL_CMD_MAX);
MPASS(family_id != 0);
ret = genl_register_cmds(PFLOWNL_FAMILY_NAME, pflow_cmds,
nitems(pflow_cmds));
ret = genl_register_cmds(family_id, pflow_cmds, nitems(pflow_cmds));
return (ret ? 0 : ENODEV);
}
@ -1808,7 +1808,7 @@ static void
pflow_uninit(void)
{
osd_jail_deregister(pflow_do_osd_jail_slot);
genl_unregister_family(PFLOWNL_FAMILY_NAME);
genl_unregister_family(family_id);
}
static int

View file

@ -167,8 +167,7 @@ rpcnl_init(void)
rpcnl_family_id = genl_register_family(rpcnl_family_name, 0, 1, 1);
MPASS(rpcnl_family_id != 0);
rv = genl_register_cmds(rpcnl_family_name, clnt_cmds,
nitems(clnt_cmds));
rv = genl_register_cmds(rpcnl_family_id, clnt_cmds, nitems(clnt_cmds));
MPASS(rv);
rw_init(&rpcnl_global_lock, rpcnl_family_name);
}
@ -185,7 +184,7 @@ client_nl_create(const char *name, const rpcprog_t program,
uint32_t group;
bool rv __diagused;
if ((group = genl_register_group(rpcnl_family_name, name)) == 0)
if ((group = genl_register_group(rpcnl_family_id, name)) == 0)
return (NULL);
nl = malloc(sizeof(*nl), M_RPC, M_WAITOK);

View file

@ -360,18 +360,17 @@ static const struct genl_cmd ktest_cmds[] = {
},
};
static int family_id;
static void
ktest_nl_register(void)
{
bool ret __diagused;
int family_id __diagused;
NL_VERIFY_PARSERS(all_parsers);
family_id = genl_register_family(KTEST_FAMILY_NAME, 0, 1, KTEST_CMD_MAX);
MPASS(family_id != 0);
ret = genl_register_cmds(KTEST_FAMILY_NAME, ktest_cmds,
nitems(ktest_cmds));
ret = genl_register_cmds(family_id, ktest_cmds, nitems(ktest_cmds));
MPASS(ret);
}
@ -380,7 +379,7 @@ ktest_nl_unregister(void)
{
MPASS(TAILQ_EMPTY(&module_list));
genl_unregister_family(KTEST_FAMILY_NAME);
genl_unregister_family(family_id);
}
static int