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.
This commit is contained in:
parent
3656b6f908
commit
faef2fa45f
|
@ -110,16 +110,34 @@ struct genl_cmd
|
||||||
*
|
*
|
||||||
* Definition of a Generic Netlink family
|
* 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
|
* @see genl_cmd
|
||||||
*/
|
*/
|
||||||
struct genl_ops
|
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;
|
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;
|
char * o_name;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,12 +146,10 @@ struct genl_ops
|
||||||
*/
|
*/
|
||||||
struct nl_cache_ops * o_cache_ops;
|
struct nl_cache_ops * o_cache_ops;
|
||||||
|
|
||||||
/**
|
/** Optional array defining the available Generic Netlink commands */
|
||||||
* Can point to an array of generic netlink commands definitions.
|
|
||||||
*/
|
|
||||||
struct genl_cmd * o_cmds;
|
struct genl_cmd * o_cmds;
|
||||||
|
|
||||||
/** Size of \c o_cmds array */
|
/** Number of elements in \c o_cmds array */
|
||||||
int o_ncmds;
|
int o_ncmds;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -143,6 +159,9 @@ struct genl_ops
|
||||||
struct nl_list_head o_list;
|
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 int genl_register(struct nl_cache_ops *);
|
||||||
extern void genl_unregister(struct nl_cache_ops *);
|
extern void genl_unregister(struct nl_cache_ops *);
|
||||||
|
|
179
lib/genl/mngt.c
179
lib/genl/mngt.c
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ingroup genl
|
* @ingroup genl
|
||||||
* @defgroup genl_mngt Family and Operations Management
|
* @defgroup genl_mngt Family and Command Registration
|
||||||
*
|
*
|
||||||
* Registering Generic Netlink Families and Commands
|
* Registering Generic Netlink Families and Commands
|
||||||
*
|
*
|
||||||
|
@ -30,28 +30,34 @@
|
||||||
|
|
||||||
static NL_LIST_HEAD(genl_ops_list);
|
static NL_LIST_HEAD(genl_ops_list);
|
||||||
|
|
||||||
static int genl_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
|
static struct genl_cmd *lookup_cmd(struct genl_ops *ops, int cmd_id)
|
||||||
struct nlmsghdr *nlh, struct nl_parser_param *pp)
|
|
||||||
{
|
{
|
||||||
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 genlmsghdr *ghdr;
|
||||||
struct genl_cmd *cmd;
|
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;
|
err = -NLE_MSGTYPE_NOSUPPORT;
|
||||||
goto errout;
|
goto errout;
|
||||||
|
}
|
||||||
|
|
||||||
found:
|
|
||||||
if (cmd->c_msg_parser == NULL)
|
if (cmd->c_msg_parser == NULL)
|
||||||
err = -NLE_OPNOTSUPP;
|
err = -NLE_OPNOTSUPP;
|
||||||
else {
|
else {
|
||||||
|
@ -64,25 +70,57 @@ found:
|
||||||
.attrs = tb,
|
.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);
|
cmd->c_attr_policy);
|
||||||
if (err < 0)
|
if (err < 0)
|
||||||
goto errout;
|
goto errout;
|
||||||
|
|
||||||
err = cmd->c_msg_parser(ops, cmd, &info, pp);
|
err = cmd->c_msg_parser(cache_ops, cmd, &info, arg);
|
||||||
}
|
}
|
||||||
errout:
|
errout:
|
||||||
return err;
|
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)
|
char *genl_op2name(int family, int op, char *buf, size_t len)
|
||||||
{
|
{
|
||||||
struct genl_ops *ops;
|
struct genl_ops *ops;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
nl_list_for_each_entry(ops, &genl_ops_list, o_list) {
|
if ((ops = lookup_family(family))) {
|
||||||
if (ops->o_family == family) {
|
|
||||||
for (i = 0; i < ops->o_ncmds; i++) {
|
for (i = 0; i < ops->o_ncmds; i++) {
|
||||||
struct genl_cmd *cmd;
|
struct genl_cmd *cmd;
|
||||||
cmd = &ops->o_cmds[i];
|
cmd = &ops->o_cmds[i];
|
||||||
|
@ -93,7 +131,6 @@ char *genl_op2name(int family, int op, char *buf, size_t len)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
strncpy(buf, "unknown", len - 1);
|
strncpy(buf, "unknown", len - 1);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -102,7 +139,90 @@ char *genl_op2name(int family, int op, char *buf, size_t len)
|
||||||
/** @endcond */
|
/** @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
|
* Register Generic Netlink family backed cache
|
||||||
* @arg ops Cache operations definition
|
* @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.
|
* @return 0 on success or a negative error code.
|
||||||
*/
|
*/
|
||||||
int genl_register(struct nl_cache_ops *ops)
|
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_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_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;
|
ops->co_msg_parser = genl_msg_parser;
|
||||||
|
|
||||||
/* FIXME: check for dup */
|
if ((err = genl_register_family(ops->co_genl)) < 0)
|
||||||
|
goto errout;
|
||||||
nl_list_add_tail(&ops->co_genl->o_list, &genl_ops_list);
|
|
||||||
|
|
||||||
err = nl_cache_mngt_register(ops);
|
err = nl_cache_mngt_register(ops);
|
||||||
errout:
|
errout:
|
||||||
|
@ -155,7 +281,8 @@ void genl_unregister(struct nl_cache_ops *ops)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
nl_cache_mngt_unregister(ops);
|
nl_cache_mngt_unregister(ops);
|
||||||
nl_list_del(&ops->co_genl->o_list);
|
|
||||||
|
genl_unregister_family(ops->co_genl);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @} */
|
/** @} */
|
||||||
|
|
|
@ -81,9 +81,9 @@ static void print(struct nl_cache_ops *ops, void *arg)
|
||||||
|
|
||||||
printf(" genl:\n" \
|
printf(" genl:\n" \
|
||||||
" name: %s\n" \
|
" name: %s\n" \
|
||||||
" family: %d\n" \
|
" user-hdr: %d\n" \
|
||||||
" id: %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) {
|
if (genl_ops->o_ncmds) {
|
||||||
int i;
|
int i;
|
||||||
|
|
|
@ -1,4 +1,72 @@
|
||||||
#include <netlink/cli/utils.h>
|
#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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
@ -10,25 +78,36 @@ int main(int argc, char *argv[])
|
||||||
sock = nl_cli_alloc_socket();
|
sock = nl_cli_alloc_socket();
|
||||||
nl_cli_connect(sock, NETLINK_GENERIC);
|
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();
|
msg = nlmsg_alloc();
|
||||||
if (msg == NULL)
|
if (msg == NULL)
|
||||||
nl_cli_fatal(NLE_NOMEM, "Unable to allocate netlink message");
|
nl_cli_fatal(NLE_NOMEM, "Unable to allocate netlink message");
|
||||||
|
|
||||||
hdr = genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, GENL_ID_CTRL,
|
hdr = genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, ops.o_id,
|
||||||
0, 0, CTRL_CMD_GETFAMILY, 1);
|
0, 0, TASKSTATS_CMD_GET, TASKSTATS_GENL_VERSION);
|
||||||
if (hdr == NULL)
|
if (hdr == NULL)
|
||||||
nl_cli_fatal(ENOMEM, "Unable to write genl header");
|
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));
|
nl_cli_fatal(err, "Unable to add attribute: %s", nl_geterror(err));
|
||||||
|
|
||||||
if ((err = nl_send_auto_complete(sock, msg)) < 0)
|
if ((err = nl_send_auto_complete(sock, msg)) < 0)
|
||||||
nl_cli_fatal(err, "Unable to send message: %s", nl_geterror(err));
|
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)
|
if ((err = nl_recvmsgs_default(sock)) < 0)
|
||||||
nl_cli_fatal(err, "Unable to receive message: %s", nl_geterror(err));
|
nl_cli_fatal(err, "Unable to receive message: %s", nl_geterror(err));
|
||||||
|
|
||||||
nlmsg_free(msg);
|
|
||||||
nl_close(sock);
|
nl_close(sock);
|
||||||
nl_socket_free(sock);
|
nl_socket_free(sock);
|
||||||
|
|
||||||
|
|
Reference in New Issue