From faef2fa45f67da810415f4dd4f0ce62d70965ad4 Mon Sep 17 00:00:00 2001 From: Thomas Graf Date: Fri, 1 Jun 2012 11:48:08 +0200 Subject: [PATCH] genl: Support registration of families without depending on caches Introduces the functions genl_register_family() and genl_unregister_family() to register a Generic Netlink family which does not implement a cachable type. API users can direct received messages into genl_handle_msg() which will validate the messages and call the callback functions defined in the commands definition. See test/test-genl.c for an example on how to use it. --- include/netlink/genl/mngt.h | 33 ++++-- lib/genl/mngt.c | 193 ++++++++++++++++++++++++++++++------ src/nl-list-caches.c | 4 +- tests/test-genl.c | 87 +++++++++++++++- 4 files changed, 271 insertions(+), 46 deletions(-) diff --git a/include/netlink/genl/mngt.h b/include/netlink/genl/mngt.h index 18d3866..01cbdb5 100644 --- a/include/netlink/genl/mngt.h +++ b/include/netlink/genl/mngt.h @@ -110,16 +110,34 @@ struct genl_cmd * * Definition of a Generic Netlink family * + * @par Example: + * @code + * static struct genl_cmd foo_cmds[] = { + * [...] + * }; + * + * static struct genl_ops my_genl_ops = { + * .o_name = "foo", + * .o_hdrsize = sizeof(struct my_hdr), + * .o_cmds = foo_cmds, + * .o_ncmds = ARRAY_SIZE(foo_cmds), + * }; + * + * if ((err = genl_register_family(&my_genl_ops)) < 0) + * // ERROR + * @endcode + * * @see genl_cmd */ struct genl_ops { - int o_family; + /** Length of user header */ + unsigned int o_hdrsize; - /** Numeric identifier, automatically resolved by genl_mngt_resolve() */ + /** Numeric identifier, automatically filled in by genl_ops_resolve() */ int o_id; - /** Human readable name, used to resolve to numeric identifier */ + /** Human readable name, used by genl_ops_resolve() to resolve numeric id */ char * o_name; /** @@ -128,12 +146,10 @@ struct genl_ops */ struct nl_cache_ops * o_cache_ops; - /** - * Can point to an array of generic netlink commands definitions. - */ + /** Optional array defining the available Generic Netlink commands */ struct genl_cmd * o_cmds; - /** Size of \c o_cmds array */ + /** Number of elements in \c o_cmds array */ int o_ncmds; /** @@ -143,6 +159,9 @@ struct genl_ops struct nl_list_head o_list; }; +extern int genl_register_family(struct genl_ops *); +extern int genl_unregister_family(struct genl_ops *); +extern int genl_handle_msg(struct nl_msg *, void *); extern int genl_register(struct nl_cache_ops *); extern void genl_unregister(struct nl_cache_ops *); diff --git a/lib/genl/mngt.c b/lib/genl/mngt.c index f53aa8a..ad25172 100644 --- a/lib/genl/mngt.c +++ b/lib/genl/mngt.c @@ -11,7 +11,7 @@ /** * @ingroup genl - * @defgroup genl_mngt Family and Operations Management + * @defgroup genl_mngt Family and Command Registration * * Registering Generic Netlink Families and Commands * @@ -30,28 +30,34 @@ static NL_LIST_HEAD(genl_ops_list); -static int genl_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who, - struct nlmsghdr *nlh, struct nl_parser_param *pp) +static struct genl_cmd *lookup_cmd(struct genl_ops *ops, int cmd_id) { - int i, err; + struct genl_cmd *cmd; + int i; + + for (i = 0; i < ops->o_ncmds; i++) { + cmd = &ops->o_cmds[i]; + if (cmd->c_id == cmd_id) + return cmd; + } + + return NULL; +} + +static int cmd_msg_parser(struct sockaddr_nl *who, struct nlmsghdr *nlh, + struct genl_ops *ops, struct nl_cache_ops *cache_ops, void *arg) +{ + int err; struct genlmsghdr *ghdr; struct genl_cmd *cmd; - ghdr = nlmsg_data(nlh); + ghdr = genlmsg_hdr(nlh); - if (ops->co_genl == NULL) - BUG(); - - for (i = 0; i < ops->co_genl->o_ncmds; i++) { - cmd = &ops->co_genl->o_cmds[i]; - if (cmd->c_id == ghdr->cmd) - goto found; + if (!(cmd = lookup_cmd(ops, ghdr->cmd))) { + err = -NLE_MSGTYPE_NOSUPPORT; + goto errout; } - err = -NLE_MSGTYPE_NOSUPPORT; - goto errout; - -found: if (cmd->c_msg_parser == NULL) err = -NLE_OPNOTSUPP; else { @@ -64,33 +70,64 @@ found: .attrs = tb, }; - err = nlmsg_parse(nlh, ops->co_hdrsize, tb, cmd->c_maxattr, + err = nlmsg_parse(nlh, GENL_HDRSIZE(ops->o_hdrsize), tb, cmd->c_maxattr, cmd->c_attr_policy); if (err < 0) goto errout; - err = cmd->c_msg_parser(ops, cmd, &info, pp); + err = cmd->c_msg_parser(cache_ops, cmd, &info, arg); } errout: return err; } +static int genl_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who, + struct nlmsghdr *nlh, struct nl_parser_param *pp) +{ + if (ops->co_genl == NULL) + BUG(); + + return cmd_msg_parser(who, nlh, ops->co_genl, ops, pp); +} + +static struct genl_ops *lookup_family(int family) +{ + struct genl_ops *ops; + + nl_list_for_each_entry(ops, &genl_ops_list, o_list) { + if (ops->o_id == family) + return ops; + } + + return NULL; +} + +static struct genl_ops *lookup_family_by_name(const char *name) +{ + struct genl_ops *ops; + + nl_list_for_each_entry(ops, &genl_ops_list, o_list) { + if (!strcmp(ops->o_name, name)) + return ops; + } + + return NULL; +} + char *genl_op2name(int family, int op, char *buf, size_t len) { struct genl_ops *ops; int i; - nl_list_for_each_entry(ops, &genl_ops_list, o_list) { - if (ops->o_family == family) { - for (i = 0; i < ops->o_ncmds; i++) { - struct genl_cmd *cmd; - cmd = &ops->o_cmds[i]; + if ((ops = lookup_family(family))) { + for (i = 0; i < ops->o_ncmds; i++) { + struct genl_cmd *cmd; + cmd = &ops->o_cmds[i]; - if (cmd->c_id == op) { - strncpy(buf, cmd->c_name, len - 1); - return buf; - } + if (cmd->c_id == op) { + strncpy(buf, cmd->c_name, len - 1); + return buf; } } } @@ -102,7 +139,90 @@ char *genl_op2name(int family, int op, char *buf, size_t len) /** @endcond */ /** - * @name Registration (Cache Based) + * @name Registration + * @{ + */ + +/** + * Register Generic Netlink family and associated commands + * @arg ops Generic Netlink family definition + * + * Registers the specified Generic Netlink family definition together with + * all associated commands. After registration, received Generic Netlink + * messages can be passed to genl_handle_msg() which will validate the + * messages, look for a matching command and call the respective callback + * function automatically. + * + * @note Consider using genl_register() if the family is used to implement a + * cacheable type. + * + * @see genl_unregister_family(); + * @see genl_register(); + * + * @return 0 on success or a negative error code. + */ +int genl_register_family(struct genl_ops *ops) +{ + if (!ops->o_name) + return -NLE_INVAL; + + if (ops->o_cmds && ops->o_ncmds <= 0) + return -NLE_INVAL; + + if (ops->o_id && lookup_family(ops->o_id)) + return -NLE_EXIST; + + if (lookup_family_by_name(ops->o_name)) + return -NLE_EXIST; + + nl_list_add_tail(&ops->o_list, &genl_ops_list); + + return 0; +} + +/** + * Unregister Generic Netlink family + * @arg ops Generic Netlink family definition + * + * Unregisters a family and all associated commands that were previously + * registered using genl_register_family(). + * + * @see genl_register_family() + * + * @return 0 on success or a negative error code. + */ +int genl_unregister_family(struct genl_ops *ops) +{ + nl_list_del(&ops->o_list); + + return 0; +} + +/** + * Run a received message through the demultiplexer + * @arg msg Generic Netlink message + * @arg arg Argument passed on to the message handler callback + * + * @return 0 on success or a negative error code. + */ +int genl_handle_msg(struct nl_msg *msg, void *arg) +{ + struct nlmsghdr *nlh = nlmsg_hdr(msg); + struct genl_ops *ops; + + if (!genlmsg_valid_hdr(nlh, 0)) + return -NLE_INVAL; + + if (!(ops = lookup_family(nlh->nlmsg_type))) + return -NLE_MSGTYPE_NOSUPPORT; + + return cmd_msg_parser(nlmsg_get_src(msg), nlh, ops, NULL, arg); +} + +/** @} */ + +/** + * @name Registration of Cache Operations * @{ */ @@ -110,6 +230,12 @@ char *genl_op2name(int family, int op, char *buf, size_t len) * Register Generic Netlink family backed cache * @arg ops Cache operations definition * + * Same as genl_register_family() but additionally registers the specified + * cache operations using nl_cache_mngt_register() and associates it with + * the Generic Netlink family. + * + * @see genl_register_family() + * * @return 0 on success or a negative error code. */ int genl_register(struct nl_cache_ops *ops) @@ -132,13 +258,13 @@ int genl_register(struct nl_cache_ops *ops) } ops->co_genl->o_cache_ops = ops; + ops->co_genl->o_hdrsize = ops->co_hdrsize - GENL_HDRLEN; ops->co_genl->o_name = ops->co_msgtypes[0].mt_name; - ops->co_genl->o_family = ops->co_msgtypes[0].mt_id; + ops->co_genl->o_id = ops->co_msgtypes[0].mt_id; ops->co_msg_parser = genl_msg_parser; - /* FIXME: check for dup */ - - nl_list_add_tail(&ops->co_genl->o_list, &genl_ops_list); + if ((err = genl_register_family(ops->co_genl)) < 0) + goto errout; err = nl_cache_mngt_register(ops); errout: @@ -155,7 +281,8 @@ void genl_unregister(struct nl_cache_ops *ops) return; nl_cache_mngt_unregister(ops); - nl_list_del(&ops->co_genl->o_list); + + genl_unregister_family(ops->co_genl); } /** @} */ diff --git a/src/nl-list-caches.c b/src/nl-list-caches.c index 3c35dd5..14cbab1 100644 --- a/src/nl-list-caches.c +++ b/src/nl-list-caches.c @@ -81,9 +81,9 @@ static void print(struct nl_cache_ops *ops, void *arg) printf(" genl:\n" \ " name: %s\n" \ - " family: %d\n" \ + " user-hdr: %d\n" \ " id: %d\n", - genl_ops->o_name, genl_ops->o_family, genl_ops->o_id); + genl_ops->o_name, genl_ops->o_hdrsize, genl_ops->o_id); if (genl_ops->o_ncmds) { int i; diff --git a/tests/test-genl.c b/tests/test-genl.c index 2706a92..63862b3 100644 --- a/tests/test-genl.c +++ b/tests/test-genl.c @@ -1,4 +1,72 @@ #include +#include + +static struct nla_policy attr_policy[TASKSTATS_TYPE_MAX+1] = { + [TASKSTATS_TYPE_PID] = { .type = NLA_U32 }, + [TASKSTATS_TYPE_TGID] = { .type = NLA_U32 }, + [TASKSTATS_TYPE_STATS] = { .minlen = sizeof(struct taskstats) }, + [TASKSTATS_TYPE_AGGR_PID] = { .type = NLA_NESTED }, + [TASKSTATS_TYPE_AGGR_TGID] = { .type = NLA_NESTED }, +}; + + +static int parse_cmd_new(struct nl_cache_ops *unused, struct genl_cmd *cmd, + struct genl_info *info, void *arg) +{ + struct nlattr *attrs[TASKSTATS_TYPE_MAX+1]; + struct nlattr *nested; + int err; + + if (info->attrs[TASKSTATS_TYPE_AGGR_PID]) + nested = info->attrs[TASKSTATS_TYPE_AGGR_PID]; + else if (info->attrs[TASKSTATS_TYPE_AGGR_TGID]) + nested = info->attrs[TASKSTATS_TYPE_AGGR_TGID]; + else { + fprintf(stderr, "Invalid taskstats message: Unable to find " + "nested attribute/\n"); + return NL_SKIP; + } + + err = nla_parse_nested(attrs, TASKSTATS_TYPE_MAX, nested, attr_policy); + if (err < 0) { + nl_perror(err, "Error while parsing generic netlink message"); + return err; + } + + + if (attrs[TASKSTATS_TYPE_STATS]) { + struct taskstats *stats = nla_data(attrs[TASKSTATS_TYPE_STATS]); + + printf("%s pid %u uid %u gid %u parent %u\n", + stats->ac_comm, stats->ac_pid, stats->ac_uid, + stats->ac_gid, stats->ac_ppid); + } + + return 0; +} + +static int parse_cb(struct nl_msg *msg, void *arg) +{ + return genl_handle_msg(msg, NULL); +} + +static struct genl_cmd cmds[] = { + { + .c_id = TASKSTATS_CMD_NEW, + .c_name = "taskstats_new()", + .c_maxattr = TASKSTATS_TYPE_MAX, + .c_attr_policy = attr_policy, + .c_msg_parser = &parse_cmd_new, + }, +}; + +#define ARRAY_SIZE(X) (sizeof(X) / sizeof((X)[0])) + +static struct genl_ops ops = { + .o_name = TASKSTATS_GENL_NAME, + .o_cmds = cmds, + .o_ncmds = ARRAY_SIZE(cmds), +}; int main(int argc, char *argv[]) { @@ -10,25 +78,36 @@ int main(int argc, char *argv[]) sock = nl_cli_alloc_socket(); nl_cli_connect(sock, NETLINK_GENERIC); + if ((err = genl_register_family(&ops)) < 0) + nl_cli_fatal(err, "Unable to register Generic Netlink family"); + + if ((err = genl_ops_resolve(sock, &ops)) < 0) + nl_cli_fatal(err, "Unable to resolve family name"); + msg = nlmsg_alloc(); if (msg == NULL) nl_cli_fatal(NLE_NOMEM, "Unable to allocate netlink message"); - hdr = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, GENL_ID_CTRL, - 0, 0, CTRL_CMD_GETFAMILY, 1); + hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, ops.o_id, + 0, 0, TASKSTATS_CMD_GET, TASKSTATS_GENL_VERSION); if (hdr == NULL) nl_cli_fatal(ENOMEM, "Unable to write genl header"); - if ((err = nla_put_u32(msg, CTRL_ATTR_FAMILY_ID, GENL_ID_CTRL)) < 0) + if ((err = nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, 1)) < 0) nl_cli_fatal(err, "Unable to add attribute: %s", nl_geterror(err)); if ((err = nl_send_auto_complete(sock, msg)) < 0) nl_cli_fatal(err, "Unable to send message: %s", nl_geterror(err)); + nlmsg_free(msg); + + if ((err = nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, + parse_cb, NULL)) < 0) + nl_cli_fatal(err, "Unable to modify valid message callback"); + if ((err = nl_recvmsgs_default(sock)) < 0) nl_cli_fatal(err, "Unable to receive message: %s", nl_geterror(err)); - nlmsg_free(msg); nl_close(sock); nl_socket_free(sock);