d15c345fe3
Add a new kernel subsystem, NetLabel, to provide explicit packet labeling services (CIPSO, RIPSO, etc.) to LSM developers. NetLabel is designed to work in conjunction with a LSM to intercept and decode security labels on incoming network packets as well as ensure that outgoing network packets are labeled according to the security mechanism employed by the LSM. The NetLabel subsystem is configured through a Generic NETLINK interface described in the header files included in this patch. Signed-off-by: Paul Moore <paul.moore@hp.com> Signed-off-by: David S. Miller <davem@davemloft.net>
625 lines
15 KiB
C
625 lines
15 KiB
C
/*
|
|
* NetLabel Management Support
|
|
*
|
|
* This file defines the management functions for the NetLabel system. The
|
|
* NetLabel system manages static and dynamic label mappings for network
|
|
* protocols such as CIPSO and RIPSO.
|
|
*
|
|
* Author: Paul Moore <paul.moore@hp.com>
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* (c) Copyright Hewlett-Packard Development Company, L.P., 2006
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
|
|
* the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*
|
|
*/
|
|
|
|
#include <linux/types.h>
|
|
#include <linux/socket.h>
|
|
#include <linux/string.h>
|
|
#include <linux/skbuff.h>
|
|
#include <net/sock.h>
|
|
#include <net/netlink.h>
|
|
#include <net/genetlink.h>
|
|
#include <net/netlabel.h>
|
|
#include <net/cipso_ipv4.h>
|
|
|
|
#include "netlabel_domainhash.h"
|
|
#include "netlabel_user.h"
|
|
#include "netlabel_mgmt.h"
|
|
|
|
/* NetLabel Generic NETLINK CIPSOv4 family */
|
|
static struct genl_family netlbl_mgmt_gnl_family = {
|
|
.id = GENL_ID_GENERATE,
|
|
.hdrsize = 0,
|
|
.name = NETLBL_NLTYPE_MGMT_NAME,
|
|
.version = NETLBL_PROTO_VERSION,
|
|
.maxattr = 0,
|
|
};
|
|
|
|
|
|
/*
|
|
* NetLabel Command Handlers
|
|
*/
|
|
|
|
/**
|
|
* netlbl_mgmt_add - Handle an ADD message
|
|
* @skb: the NETLINK buffer
|
|
* @info: the Generic NETLINK info block
|
|
*
|
|
* Description:
|
|
* Process a user generated ADD message and add the domains from the message
|
|
* to the hash table. See netlabel.h for a description of the message format.
|
|
* Returns zero on success, negative values on failure.
|
|
*
|
|
*/
|
|
static int netlbl_mgmt_add(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
int ret_val = -EINVAL;
|
|
struct nlattr *msg_ptr = netlbl_netlink_payload_data(skb);
|
|
int msg_len = netlbl_netlink_payload_len(skb);
|
|
u32 count;
|
|
struct netlbl_dom_map *entry = NULL;
|
|
u32 iter;
|
|
u32 tmp_val;
|
|
int tmp_size;
|
|
|
|
ret_val = netlbl_netlink_cap_check(skb, CAP_NET_ADMIN);
|
|
if (ret_val != 0)
|
|
goto add_failure;
|
|
|
|
if (msg_len < NETLBL_LEN_U32)
|
|
goto add_failure;
|
|
count = netlbl_getinc_u32(&msg_ptr, &msg_len);
|
|
|
|
for (iter = 0; iter < count && msg_len > 0; iter++, entry = NULL) {
|
|
if (msg_len <= 0) {
|
|
ret_val = -EINVAL;
|
|
goto add_failure;
|
|
}
|
|
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
|
if (entry == NULL) {
|
|
ret_val = -ENOMEM;
|
|
goto add_failure;
|
|
}
|
|
tmp_size = nla_len(msg_ptr);
|
|
if (tmp_size <= 0 || tmp_size > msg_len) {
|
|
ret_val = -EINVAL;
|
|
goto add_failure;
|
|
}
|
|
entry->domain = kmalloc(tmp_size, GFP_KERNEL);
|
|
if (entry->domain == NULL) {
|
|
ret_val = -ENOMEM;
|
|
goto add_failure;
|
|
}
|
|
nla_strlcpy(entry->domain, msg_ptr, tmp_size);
|
|
entry->domain[tmp_size - 1] = '\0';
|
|
msg_ptr = nla_next(msg_ptr, &msg_len);
|
|
|
|
if (msg_len < NETLBL_LEN_U32) {
|
|
ret_val = -EINVAL;
|
|
goto add_failure;
|
|
}
|
|
tmp_val = netlbl_getinc_u32(&msg_ptr, &msg_len);
|
|
entry->type = tmp_val;
|
|
switch (tmp_val) {
|
|
case NETLBL_NLTYPE_UNLABELED:
|
|
ret_val = netlbl_domhsh_add(entry);
|
|
break;
|
|
case NETLBL_NLTYPE_CIPSOV4:
|
|
if (msg_len < NETLBL_LEN_U32) {
|
|
ret_val = -EINVAL;
|
|
goto add_failure;
|
|
}
|
|
tmp_val = netlbl_getinc_u32(&msg_ptr, &msg_len);
|
|
/* We should be holding a rcu_read_lock() here
|
|
* while we hold the result but since the entry
|
|
* will always be deleted when the CIPSO DOI
|
|
* is deleted we aren't going to keep the lock. */
|
|
rcu_read_lock();
|
|
entry->type_def.cipsov4 = cipso_v4_doi_getdef(tmp_val);
|
|
if (entry->type_def.cipsov4 == NULL) {
|
|
rcu_read_unlock();
|
|
ret_val = -EINVAL;
|
|
goto add_failure;
|
|
}
|
|
ret_val = netlbl_domhsh_add(entry);
|
|
rcu_read_unlock();
|
|
break;
|
|
default:
|
|
ret_val = -EINVAL;
|
|
}
|
|
if (ret_val != 0)
|
|
goto add_failure;
|
|
}
|
|
|
|
netlbl_netlink_send_ack(info,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_ACK,
|
|
NETLBL_E_OK);
|
|
return 0;
|
|
|
|
add_failure:
|
|
if (entry)
|
|
kfree(entry->domain);
|
|
kfree(entry);
|
|
netlbl_netlink_send_ack(info,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_ACK,
|
|
-ret_val);
|
|
return ret_val;
|
|
}
|
|
|
|
/**
|
|
* netlbl_mgmt_remove - Handle a REMOVE message
|
|
* @skb: the NETLINK buffer
|
|
* @info: the Generic NETLINK info block
|
|
*
|
|
* Description:
|
|
* Process a user generated REMOVE message and remove the specified domain
|
|
* mappings. Returns zero on success, negative values on failure.
|
|
*
|
|
*/
|
|
static int netlbl_mgmt_remove(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
int ret_val = -EINVAL;
|
|
struct nlattr *msg_ptr = netlbl_netlink_payload_data(skb);
|
|
int msg_len = netlbl_netlink_payload_len(skb);
|
|
u32 count;
|
|
u32 iter;
|
|
int tmp_size;
|
|
unsigned char *domain;
|
|
|
|
ret_val = netlbl_netlink_cap_check(skb, CAP_NET_ADMIN);
|
|
if (ret_val != 0)
|
|
goto remove_return;
|
|
|
|
if (msg_len < NETLBL_LEN_U32)
|
|
goto remove_return;
|
|
count = netlbl_getinc_u32(&msg_ptr, &msg_len);
|
|
|
|
for (iter = 0; iter < count && msg_len > 0; iter++) {
|
|
if (msg_len <= 0) {
|
|
ret_val = -EINVAL;
|
|
goto remove_return;
|
|
}
|
|
tmp_size = nla_len(msg_ptr);
|
|
domain = nla_data(msg_ptr);
|
|
if (tmp_size <= 0 || tmp_size > msg_len ||
|
|
domain[tmp_size - 1] != '\0') {
|
|
ret_val = -EINVAL;
|
|
goto remove_return;
|
|
}
|
|
ret_val = netlbl_domhsh_remove(domain);
|
|
if (ret_val != 0)
|
|
goto remove_return;
|
|
msg_ptr = nla_next(msg_ptr, &msg_len);
|
|
}
|
|
|
|
ret_val = 0;
|
|
|
|
remove_return:
|
|
netlbl_netlink_send_ack(info,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_ACK,
|
|
-ret_val);
|
|
return ret_val;
|
|
}
|
|
|
|
/**
|
|
* netlbl_mgmt_list - Handle a LIST message
|
|
* @skb: the NETLINK buffer
|
|
* @info: the Generic NETLINK info block
|
|
*
|
|
* Description:
|
|
* Process a user generated LIST message and dumps the domain hash table in a
|
|
* form suitable for use in a kernel generated LIST message. Returns zero on
|
|
* success, negative values on failure.
|
|
*
|
|
*/
|
|
static int netlbl_mgmt_list(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
int ret_val = -ENOMEM;
|
|
struct sk_buff *ans_skb;
|
|
|
|
ans_skb = netlbl_domhsh_dump(NLMSG_SPACE(GENL_HDRLEN));
|
|
if (ans_skb == NULL)
|
|
goto list_failure;
|
|
netlbl_netlink_hdr_push(ans_skb,
|
|
info->snd_pid,
|
|
0,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_LIST);
|
|
|
|
ret_val = netlbl_netlink_snd(ans_skb, info->snd_pid);
|
|
if (ret_val != 0)
|
|
goto list_failure;
|
|
|
|
return 0;
|
|
|
|
list_failure:
|
|
netlbl_netlink_send_ack(info,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_ACK,
|
|
-ret_val);
|
|
return ret_val;
|
|
}
|
|
|
|
/**
|
|
* netlbl_mgmt_adddef - Handle an ADDDEF message
|
|
* @skb: the NETLINK buffer
|
|
* @info: the Generic NETLINK info block
|
|
*
|
|
* Description:
|
|
* Process a user generated ADDDEF message and respond accordingly. Returns
|
|
* zero on success, negative values on failure.
|
|
*
|
|
*/
|
|
static int netlbl_mgmt_adddef(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
int ret_val = -EINVAL;
|
|
struct nlattr *msg_ptr = netlbl_netlink_payload_data(skb);
|
|
int msg_len = netlbl_netlink_payload_len(skb);
|
|
struct netlbl_dom_map *entry = NULL;
|
|
u32 tmp_val;
|
|
|
|
ret_val = netlbl_netlink_cap_check(skb, CAP_NET_ADMIN);
|
|
if (ret_val != 0)
|
|
goto adddef_failure;
|
|
|
|
if (msg_len < NETLBL_LEN_U32)
|
|
goto adddef_failure;
|
|
tmp_val = netlbl_getinc_u32(&msg_ptr, &msg_len);
|
|
|
|
entry = kzalloc(sizeof(*entry), GFP_KERNEL);
|
|
if (entry == NULL) {
|
|
ret_val = -ENOMEM;
|
|
goto adddef_failure;
|
|
}
|
|
|
|
entry->type = tmp_val;
|
|
switch (entry->type) {
|
|
case NETLBL_NLTYPE_UNLABELED:
|
|
ret_val = netlbl_domhsh_add_default(entry);
|
|
break;
|
|
case NETLBL_NLTYPE_CIPSOV4:
|
|
if (msg_len < NETLBL_LEN_U32) {
|
|
ret_val = -EINVAL;
|
|
goto adddef_failure;
|
|
}
|
|
tmp_val = netlbl_getinc_u32(&msg_ptr, &msg_len);
|
|
/* We should be holding a rcu_read_lock here while we
|
|
* hold the result but since the entry will always be
|
|
* deleted when the CIPSO DOI is deleted we are going
|
|
* to skip the lock. */
|
|
rcu_read_lock();
|
|
entry->type_def.cipsov4 = cipso_v4_doi_getdef(tmp_val);
|
|
if (entry->type_def.cipsov4 == NULL) {
|
|
rcu_read_unlock();
|
|
ret_val = -EINVAL;
|
|
goto adddef_failure;
|
|
}
|
|
ret_val = netlbl_domhsh_add_default(entry);
|
|
rcu_read_unlock();
|
|
break;
|
|
default:
|
|
ret_val = -EINVAL;
|
|
}
|
|
if (ret_val != 0)
|
|
goto adddef_failure;
|
|
|
|
netlbl_netlink_send_ack(info,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_ACK,
|
|
NETLBL_E_OK);
|
|
return 0;
|
|
|
|
adddef_failure:
|
|
kfree(entry);
|
|
netlbl_netlink_send_ack(info,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_ACK,
|
|
-ret_val);
|
|
return ret_val;
|
|
}
|
|
|
|
/**
|
|
* netlbl_mgmt_removedef - Handle a REMOVEDEF message
|
|
* @skb: the NETLINK buffer
|
|
* @info: the Generic NETLINK info block
|
|
*
|
|
* Description:
|
|
* Process a user generated REMOVEDEF message and remove the default domain
|
|
* mapping. Returns zero on success, negative values on failure.
|
|
*
|
|
*/
|
|
static int netlbl_mgmt_removedef(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
int ret_val;
|
|
|
|
ret_val = netlbl_netlink_cap_check(skb, CAP_NET_ADMIN);
|
|
if (ret_val != 0)
|
|
goto removedef_return;
|
|
|
|
ret_val = netlbl_domhsh_remove_default();
|
|
|
|
removedef_return:
|
|
netlbl_netlink_send_ack(info,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_ACK,
|
|
-ret_val);
|
|
return ret_val;
|
|
}
|
|
|
|
/**
|
|
* netlbl_mgmt_listdef - Handle a LISTDEF message
|
|
* @skb: the NETLINK buffer
|
|
* @info: the Generic NETLINK info block
|
|
*
|
|
* Description:
|
|
* Process a user generated LISTDEF message and dumps the default domain
|
|
* mapping in a form suitable for use in a kernel generated LISTDEF message.
|
|
* Returns zero on success, negative values on failure.
|
|
*
|
|
*/
|
|
static int netlbl_mgmt_listdef(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
int ret_val = -ENOMEM;
|
|
struct sk_buff *ans_skb;
|
|
|
|
ans_skb = netlbl_domhsh_dump_default(NLMSG_SPACE(GENL_HDRLEN));
|
|
if (ans_skb == NULL)
|
|
goto listdef_failure;
|
|
netlbl_netlink_hdr_push(ans_skb,
|
|
info->snd_pid,
|
|
0,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_LISTDEF);
|
|
|
|
ret_val = netlbl_netlink_snd(ans_skb, info->snd_pid);
|
|
if (ret_val != 0)
|
|
goto listdef_failure;
|
|
|
|
return 0;
|
|
|
|
listdef_failure:
|
|
netlbl_netlink_send_ack(info,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_ACK,
|
|
-ret_val);
|
|
return ret_val;
|
|
}
|
|
|
|
/**
|
|
* netlbl_mgmt_modules - Handle a MODULES message
|
|
* @skb: the NETLINK buffer
|
|
* @info: the Generic NETLINK info block
|
|
*
|
|
* Description:
|
|
* Process a user generated MODULES message and respond accordingly.
|
|
*
|
|
*/
|
|
static int netlbl_mgmt_modules(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
int ret_val = -ENOMEM;
|
|
size_t data_size;
|
|
u32 mod_count;
|
|
struct sk_buff *ans_skb = NULL;
|
|
|
|
/* unlabeled + cipsov4 */
|
|
mod_count = 2;
|
|
|
|
data_size = GENL_HDRLEN + NETLBL_LEN_U32 + mod_count * NETLBL_LEN_U32;
|
|
ans_skb = netlbl_netlink_alloc_skb(0, data_size, GFP_KERNEL);
|
|
if (ans_skb == NULL)
|
|
goto modules_failure;
|
|
|
|
if (netlbl_netlink_hdr_put(ans_skb,
|
|
info->snd_pid,
|
|
0,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_MODULES) == NULL)
|
|
goto modules_failure;
|
|
|
|
ret_val = nla_put_u32(ans_skb, NLA_U32, mod_count);
|
|
if (ret_val != 0)
|
|
goto modules_failure;
|
|
ret_val = nla_put_u32(ans_skb, NLA_U32, NETLBL_NLTYPE_UNLABELED);
|
|
if (ret_val != 0)
|
|
goto modules_failure;
|
|
ret_val = nla_put_u32(ans_skb, NLA_U32, NETLBL_NLTYPE_CIPSOV4);
|
|
if (ret_val != 0)
|
|
goto modules_failure;
|
|
|
|
ret_val = netlbl_netlink_snd(ans_skb, info->snd_pid);
|
|
if (ret_val != 0)
|
|
goto modules_failure;
|
|
|
|
return 0;
|
|
|
|
modules_failure:
|
|
kfree_skb(ans_skb);
|
|
netlbl_netlink_send_ack(info,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_ACK,
|
|
-ret_val);
|
|
return ret_val;
|
|
}
|
|
|
|
/**
|
|
* netlbl_mgmt_version - Handle a VERSION message
|
|
* @skb: the NETLINK buffer
|
|
* @info: the Generic NETLINK info block
|
|
*
|
|
* Description:
|
|
* Process a user generated VERSION message and respond accordingly. Returns
|
|
* zero on success, negative values on failure.
|
|
*
|
|
*/
|
|
static int netlbl_mgmt_version(struct sk_buff *skb, struct genl_info *info)
|
|
{
|
|
int ret_val = -ENOMEM;
|
|
struct sk_buff *ans_skb = NULL;
|
|
|
|
ans_skb = netlbl_netlink_alloc_skb(0,
|
|
GENL_HDRLEN + NETLBL_LEN_U32,
|
|
GFP_KERNEL);
|
|
if (ans_skb == NULL)
|
|
goto version_failure;
|
|
if (netlbl_netlink_hdr_put(ans_skb,
|
|
info->snd_pid,
|
|
0,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_VERSION) == NULL)
|
|
goto version_failure;
|
|
|
|
ret_val = nla_put_u32(ans_skb, NLA_U32, NETLBL_PROTO_VERSION);
|
|
if (ret_val != 0)
|
|
goto version_failure;
|
|
|
|
ret_val = netlbl_netlink_snd(ans_skb, info->snd_pid);
|
|
if (ret_val != 0)
|
|
goto version_failure;
|
|
|
|
return 0;
|
|
|
|
version_failure:
|
|
kfree_skb(ans_skb);
|
|
netlbl_netlink_send_ack(info,
|
|
netlbl_mgmt_gnl_family.id,
|
|
NLBL_MGMT_C_ACK,
|
|
-ret_val);
|
|
return ret_val;
|
|
}
|
|
|
|
|
|
/*
|
|
* NetLabel Generic NETLINK Command Definitions
|
|
*/
|
|
|
|
static struct genl_ops netlbl_mgmt_genl_c_add = {
|
|
.cmd = NLBL_MGMT_C_ADD,
|
|
.flags = 0,
|
|
.doit = netlbl_mgmt_add,
|
|
.dumpit = NULL,
|
|
};
|
|
|
|
static struct genl_ops netlbl_mgmt_genl_c_remove = {
|
|
.cmd = NLBL_MGMT_C_REMOVE,
|
|
.flags = 0,
|
|
.doit = netlbl_mgmt_remove,
|
|
.dumpit = NULL,
|
|
};
|
|
|
|
static struct genl_ops netlbl_mgmt_genl_c_list = {
|
|
.cmd = NLBL_MGMT_C_LIST,
|
|
.flags = 0,
|
|
.doit = netlbl_mgmt_list,
|
|
.dumpit = NULL,
|
|
};
|
|
|
|
static struct genl_ops netlbl_mgmt_genl_c_adddef = {
|
|
.cmd = NLBL_MGMT_C_ADDDEF,
|
|
.flags = 0,
|
|
.doit = netlbl_mgmt_adddef,
|
|
.dumpit = NULL,
|
|
};
|
|
|
|
static struct genl_ops netlbl_mgmt_genl_c_removedef = {
|
|
.cmd = NLBL_MGMT_C_REMOVEDEF,
|
|
.flags = 0,
|
|
.doit = netlbl_mgmt_removedef,
|
|
.dumpit = NULL,
|
|
};
|
|
|
|
static struct genl_ops netlbl_mgmt_genl_c_listdef = {
|
|
.cmd = NLBL_MGMT_C_LISTDEF,
|
|
.flags = 0,
|
|
.doit = netlbl_mgmt_listdef,
|
|
.dumpit = NULL,
|
|
};
|
|
|
|
static struct genl_ops netlbl_mgmt_genl_c_modules = {
|
|
.cmd = NLBL_MGMT_C_MODULES,
|
|
.flags = 0,
|
|
.doit = netlbl_mgmt_modules,
|
|
.dumpit = NULL,
|
|
};
|
|
|
|
static struct genl_ops netlbl_mgmt_genl_c_version = {
|
|
.cmd = NLBL_MGMT_C_VERSION,
|
|
.flags = 0,
|
|
.doit = netlbl_mgmt_version,
|
|
.dumpit = NULL,
|
|
};
|
|
|
|
/*
|
|
* NetLabel Generic NETLINK Protocol Functions
|
|
*/
|
|
|
|
/**
|
|
* netlbl_mgmt_genl_init - Register the NetLabel management component
|
|
*
|
|
* Description:
|
|
* Register the NetLabel management component with the Generic NETLINK
|
|
* mechanism. Returns zero on success, negative values on failure.
|
|
*
|
|
*/
|
|
int netlbl_mgmt_genl_init(void)
|
|
{
|
|
int ret_val;
|
|
|
|
ret_val = genl_register_family(&netlbl_mgmt_gnl_family);
|
|
if (ret_val != 0)
|
|
return ret_val;
|
|
|
|
ret_val = genl_register_ops(&netlbl_mgmt_gnl_family,
|
|
&netlbl_mgmt_genl_c_add);
|
|
if (ret_val != 0)
|
|
return ret_val;
|
|
ret_val = genl_register_ops(&netlbl_mgmt_gnl_family,
|
|
&netlbl_mgmt_genl_c_remove);
|
|
if (ret_val != 0)
|
|
return ret_val;
|
|
ret_val = genl_register_ops(&netlbl_mgmt_gnl_family,
|
|
&netlbl_mgmt_genl_c_list);
|
|
if (ret_val != 0)
|
|
return ret_val;
|
|
ret_val = genl_register_ops(&netlbl_mgmt_gnl_family,
|
|
&netlbl_mgmt_genl_c_adddef);
|
|
if (ret_val != 0)
|
|
return ret_val;
|
|
ret_val = genl_register_ops(&netlbl_mgmt_gnl_family,
|
|
&netlbl_mgmt_genl_c_removedef);
|
|
if (ret_val != 0)
|
|
return ret_val;
|
|
ret_val = genl_register_ops(&netlbl_mgmt_gnl_family,
|
|
&netlbl_mgmt_genl_c_listdef);
|
|
if (ret_val != 0)
|
|
return ret_val;
|
|
ret_val = genl_register_ops(&netlbl_mgmt_gnl_family,
|
|
&netlbl_mgmt_genl_c_modules);
|
|
if (ret_val != 0)
|
|
return ret_val;
|
|
ret_val = genl_register_ops(&netlbl_mgmt_gnl_family,
|
|
&netlbl_mgmt_genl_c_version);
|
|
if (ret_val != 0)
|
|
return ret_val;
|
|
|
|
return 0;
|
|
}
|