]> git.ipfire.org Git - thirdparty/libnl.git/commitdiff
genl: Support registration of families without depending on caches
authorThomas Graf <tgraf@redhat.com>
Fri, 1 Jun 2012 09:48:08 +0000 (11:48 +0200)
committerThomas Graf <tgraf@redhat.com>
Fri, 1 Jun 2012 09:48:08 +0000 (11:48 +0200)
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
lib/genl/mngt.c
src/nl-list-caches.c
tests/test-genl.c

index 18d3866b81418d1744b7dcad6da7c7ca9b417303..01cbdb56ce3a4ddb814840fd03743a0f6559eb0b 100644 (file)
@@ -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 *);
index f53aa8ab31ff1372377ded412d984fbb33e66ac8..ad25172b5e1d6ba578ebbbe0aea15582d95fc628 100644 (file)
@@ -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
  *
 
 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 genlmsghdr *ghdr;
        struct genl_cmd *cmd;
+       int i;
 
-       ghdr = nlmsg_data(nlh);
+       for (i = 0; i < ops->o_ncmds; i++) {
+               cmd = &ops->o_cmds[i];
+               if (cmd->c_id == cmd_id)
+                       return cmd;
+       }
 
-       if (ops->co_genl == NULL)
-               BUG();
+       return NULL;
+}
 
-       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;
-       }
+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 = genlmsg_hdr(nlh);
 
-       err = -NLE_MSGTYPE_NOSUPPORT;
-       goto errout;
+       if (!(cmd = lookup_cmd(ops, ghdr->cmd))) {
+               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 (cmd->c_id == op) {
-                                       strncpy(buf, cmd->c_name, len - 1);
-                                       return buf;
-                               }
+       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;
                        }
                }
        }
@@ -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);
 }
 
 /** @} */
index 3c35dd5800ec0d363d45119fd465d8a44ac1dd1a..14cbab185763132ae152d1c114eff56fb2f803a3 100644 (file)
@@ -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;
index 2706a92c420617697eda026243a16d63c6181dd7..63862b34c54fdf8ed530850da7f9d1bbb5ec27f3 100644 (file)
@@ -1,4 +1,72 @@
 #include <netlink/cli/utils.h>
+#include <linux/taskstats.h>
+
+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);