Creation of Cybook 2416 (actually Gen4) repository
This commit is contained in:
5
net/netlink/Makefile
Normal file
5
net/netlink/Makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# Makefile for the netlink driver.
|
||||
#
|
||||
|
||||
obj-y := af_netlink.o attr.o genetlink.o
|
||||
1832
net/netlink/af_netlink.c
Normal file
1832
net/netlink/af_netlink.c
Normal file
File diff suppressed because it is too large
Load Diff
431
net/netlink/attr.c
Normal file
431
net/netlink/attr.c
Normal file
@@ -0,0 +1,431 @@
|
||||
/*
|
||||
* NETLINK Netlink attributes
|
||||
*
|
||||
* Authors: Thomas Graf <tgraf@suug.ch>
|
||||
* Alexey Kuznetsov <kuznet@ms2.inr.ac.ru>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/types.h>
|
||||
#include <net/netlink.h>
|
||||
|
||||
static u16 nla_attr_minlen[NLA_TYPE_MAX+1] __read_mostly = {
|
||||
[NLA_U8] = sizeof(u8),
|
||||
[NLA_U16] = sizeof(u16),
|
||||
[NLA_U32] = sizeof(u32),
|
||||
[NLA_U64] = sizeof(u64),
|
||||
[NLA_NESTED] = NLA_HDRLEN,
|
||||
};
|
||||
|
||||
static int validate_nla(struct nlattr *nla, int maxtype,
|
||||
struct nla_policy *policy)
|
||||
{
|
||||
struct nla_policy *pt;
|
||||
int minlen = 0, attrlen = nla_len(nla);
|
||||
|
||||
if (nla->nla_type <= 0 || nla->nla_type > maxtype)
|
||||
return 0;
|
||||
|
||||
pt = &policy[nla->nla_type];
|
||||
|
||||
BUG_ON(pt->type > NLA_TYPE_MAX);
|
||||
|
||||
switch (pt->type) {
|
||||
case NLA_FLAG:
|
||||
if (attrlen > 0)
|
||||
return -ERANGE;
|
||||
break;
|
||||
|
||||
case NLA_NUL_STRING:
|
||||
if (pt->len)
|
||||
minlen = min_t(int, attrlen, pt->len + 1);
|
||||
else
|
||||
minlen = attrlen;
|
||||
|
||||
if (!minlen || memchr(nla_data(nla), '\0', minlen) == NULL)
|
||||
return -EINVAL;
|
||||
/* fall through */
|
||||
|
||||
case NLA_STRING:
|
||||
if (attrlen < 1)
|
||||
return -ERANGE;
|
||||
|
||||
if (pt->len) {
|
||||
char *buf = nla_data(nla);
|
||||
|
||||
if (buf[attrlen - 1] == '\0')
|
||||
attrlen--;
|
||||
|
||||
if (attrlen > pt->len)
|
||||
return -ERANGE;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (pt->len)
|
||||
minlen = pt->len;
|
||||
else if (pt->type != NLA_UNSPEC)
|
||||
minlen = nla_attr_minlen[pt->type];
|
||||
|
||||
if (attrlen < minlen)
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* nla_validate - Validate a stream of attributes
|
||||
* @head: head of attribute stream
|
||||
* @len: length of attribute stream
|
||||
* @maxtype: maximum attribute type to be expected
|
||||
* @policy: validation policy
|
||||
*
|
||||
* Validates all attributes in the specified attribute stream against the
|
||||
* specified policy. Attributes with a type exceeding maxtype will be
|
||||
* ignored. See documenation of struct nla_policy for more details.
|
||||
*
|
||||
* Returns 0 on success or a negative error code.
|
||||
*/
|
||||
int nla_validate(struct nlattr *head, int len, int maxtype,
|
||||
struct nla_policy *policy)
|
||||
{
|
||||
struct nlattr *nla;
|
||||
int rem, err;
|
||||
|
||||
nla_for_each_attr(nla, head, len, rem) {
|
||||
err = validate_nla(nla, maxtype, policy);
|
||||
if (err < 0)
|
||||
goto errout;
|
||||
}
|
||||
|
||||
err = 0;
|
||||
errout:
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* nla_parse - Parse a stream of attributes into a tb buffer
|
||||
* @tb: destination array with maxtype+1 elements
|
||||
* @maxtype: maximum attribute type to be expected
|
||||
* @head: head of attribute stream
|
||||
* @len: length of attribute stream
|
||||
*
|
||||
* Parses a stream of attributes and stores a pointer to each attribute in
|
||||
* the tb array accessable via the attribute type. Attributes with a type
|
||||
* exceeding maxtype will be silently ignored for backwards compatibility
|
||||
* reasons. policy may be set to NULL if no validation is required.
|
||||
*
|
||||
* Returns 0 on success or a negative error code.
|
||||
*/
|
||||
int nla_parse(struct nlattr *tb[], int maxtype, struct nlattr *head, int len,
|
||||
struct nla_policy *policy)
|
||||
{
|
||||
struct nlattr *nla;
|
||||
int rem, err;
|
||||
|
||||
memset(tb, 0, sizeof(struct nlattr *) * (maxtype + 1));
|
||||
|
||||
nla_for_each_attr(nla, head, len, rem) {
|
||||
u16 type = nla->nla_type;
|
||||
|
||||
if (type > 0 && type <= maxtype) {
|
||||
if (policy) {
|
||||
err = validate_nla(nla, maxtype, policy);
|
||||
if (err < 0)
|
||||
goto errout;
|
||||
}
|
||||
|
||||
tb[type] = nla;
|
||||
}
|
||||
}
|
||||
|
||||
if (unlikely(rem > 0))
|
||||
printk(KERN_WARNING "netlink: %d bytes leftover after parsing "
|
||||
"attributes.\n", rem);
|
||||
|
||||
err = 0;
|
||||
errout:
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* nla_find - Find a specific attribute in a stream of attributes
|
||||
* @head: head of attribute stream
|
||||
* @len: length of attribute stream
|
||||
* @attrtype: type of attribute to look for
|
||||
*
|
||||
* Returns the first attribute in the stream matching the specified type.
|
||||
*/
|
||||
struct nlattr *nla_find(struct nlattr *head, int len, int attrtype)
|
||||
{
|
||||
struct nlattr *nla;
|
||||
int rem;
|
||||
|
||||
nla_for_each_attr(nla, head, len, rem)
|
||||
if (nla->nla_type == attrtype)
|
||||
return nla;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* nla_strlcpy - Copy string attribute payload into a sized buffer
|
||||
* @dst: where to copy the string to
|
||||
* @src: attribute to copy the string from
|
||||
* @dstsize: size of destination buffer
|
||||
*
|
||||
* Copies at most dstsize - 1 bytes into the destination buffer.
|
||||
* The result is always a valid NUL-terminated string. Unlike
|
||||
* strlcpy the destination buffer is always padded out.
|
||||
*
|
||||
* Returns the length of the source buffer.
|
||||
*/
|
||||
size_t nla_strlcpy(char *dst, const struct nlattr *nla, size_t dstsize)
|
||||
{
|
||||
size_t srclen = nla_len(nla);
|
||||
char *src = nla_data(nla);
|
||||
|
||||
if (srclen > 0 && src[srclen - 1] == '\0')
|
||||
srclen--;
|
||||
|
||||
if (dstsize > 0) {
|
||||
size_t len = (srclen >= dstsize) ? dstsize - 1 : srclen;
|
||||
|
||||
memset(dst, 0, dstsize);
|
||||
memcpy(dst, src, len);
|
||||
}
|
||||
|
||||
return srclen;
|
||||
}
|
||||
|
||||
/**
|
||||
* nla_memcpy - Copy a netlink attribute into another memory area
|
||||
* @dest: where to copy to memcpy
|
||||
* @src: netlink attribute to copy from
|
||||
* @count: size of the destination area
|
||||
*
|
||||
* Note: The number of bytes copied is limited by the length of
|
||||
* attribute's payload. memcpy
|
||||
*
|
||||
* Returns the number of bytes copied.
|
||||
*/
|
||||
int nla_memcpy(void *dest, struct nlattr *src, int count)
|
||||
{
|
||||
int minlen = min_t(int, count, nla_len(src));
|
||||
|
||||
memcpy(dest, nla_data(src), minlen);
|
||||
|
||||
return minlen;
|
||||
}
|
||||
|
||||
/**
|
||||
* nla_memcmp - Compare an attribute with sized memory area
|
||||
* @nla: netlink attribute
|
||||
* @data: memory area
|
||||
* @size: size of memory area
|
||||
*/
|
||||
int nla_memcmp(const struct nlattr *nla, const void *data,
|
||||
size_t size)
|
||||
{
|
||||
int d = nla_len(nla) - size;
|
||||
|
||||
if (d == 0)
|
||||
d = memcmp(nla_data(nla), data, size);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* nla_strcmp - Compare a string attribute against a string
|
||||
* @nla: netlink string attribute
|
||||
* @str: another string
|
||||
*/
|
||||
int nla_strcmp(const struct nlattr *nla, const char *str)
|
||||
{
|
||||
int len = strlen(str) + 1;
|
||||
int d = nla_len(nla) - len;
|
||||
|
||||
if (d == 0)
|
||||
d = memcmp(nla_data(nla), str, len);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
* __nla_reserve - reserve room for attribute on the skb
|
||||
* @skb: socket buffer to reserve room on
|
||||
* @attrtype: attribute type
|
||||
* @attrlen: length of attribute payload
|
||||
*
|
||||
* Adds a netlink attribute header to a socket buffer and reserves
|
||||
* room for the payload but does not copy it.
|
||||
*
|
||||
* The caller is responsible to ensure that the skb provides enough
|
||||
* tailroom for the attribute header and payload.
|
||||
*/
|
||||
struct nlattr *__nla_reserve(struct sk_buff *skb, int attrtype, int attrlen)
|
||||
{
|
||||
struct nlattr *nla;
|
||||
|
||||
nla = (struct nlattr *) skb_put(skb, nla_total_size(attrlen));
|
||||
nla->nla_type = attrtype;
|
||||
nla->nla_len = nla_attr_size(attrlen);
|
||||
|
||||
memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen));
|
||||
|
||||
return nla;
|
||||
}
|
||||
|
||||
/**
|
||||
* __nla_reserve_nohdr - reserve room for attribute without header
|
||||
* @skb: socket buffer to reserve room on
|
||||
* @attrlen: length of attribute payload
|
||||
*
|
||||
* Reserves room for attribute payload without a header.
|
||||
*
|
||||
* The caller is responsible to ensure that the skb provides enough
|
||||
* tailroom for the payload.
|
||||
*/
|
||||
void *__nla_reserve_nohdr(struct sk_buff *skb, int attrlen)
|
||||
{
|
||||
void *start;
|
||||
|
||||
start = skb_put(skb, NLA_ALIGN(attrlen));
|
||||
memset(start, 0, NLA_ALIGN(attrlen));
|
||||
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* nla_reserve - reserve room for attribute on the skb
|
||||
* @skb: socket buffer to reserve room on
|
||||
* @attrtype: attribute type
|
||||
* @attrlen: length of attribute payload
|
||||
*
|
||||
* Adds a netlink attribute header to a socket buffer and reserves
|
||||
* room for the payload but does not copy it.
|
||||
*
|
||||
* Returns NULL if the tailroom of the skb is insufficient to store
|
||||
* the attribute header and payload.
|
||||
*/
|
||||
struct nlattr *nla_reserve(struct sk_buff *skb, int attrtype, int attrlen)
|
||||
{
|
||||
if (unlikely(skb_tailroom(skb) < nla_total_size(attrlen)))
|
||||
return NULL;
|
||||
|
||||
return __nla_reserve(skb, attrtype, attrlen);
|
||||
}
|
||||
|
||||
/**
|
||||
* nla_reserve - reserve room for attribute without header
|
||||
* @skb: socket buffer to reserve room on
|
||||
* @len: length of attribute payload
|
||||
*
|
||||
* Reserves room for attribute payload without a header.
|
||||
*
|
||||
* Returns NULL if the tailroom of the skb is insufficient to store
|
||||
* the attribute payload.
|
||||
*/
|
||||
void *nla_reserve_nohdr(struct sk_buff *skb, int attrlen)
|
||||
{
|
||||
if (unlikely(skb_tailroom(skb) < NLA_ALIGN(attrlen)))
|
||||
return NULL;
|
||||
|
||||
return __nla_reserve_nohdr(skb, attrlen);
|
||||
}
|
||||
|
||||
/**
|
||||
* __nla_put - Add a netlink attribute to a socket buffer
|
||||
* @skb: socket buffer to add attribute to
|
||||
* @attrtype: attribute type
|
||||
* @attrlen: length of attribute payload
|
||||
* @data: head of attribute payload
|
||||
*
|
||||
* The caller is responsible to ensure that the skb provides enough
|
||||
* tailroom for the attribute header and payload.
|
||||
*/
|
||||
void __nla_put(struct sk_buff *skb, int attrtype, int attrlen,
|
||||
const void *data)
|
||||
{
|
||||
struct nlattr *nla;
|
||||
|
||||
nla = __nla_reserve(skb, attrtype, attrlen);
|
||||
memcpy(nla_data(nla), data, attrlen);
|
||||
}
|
||||
|
||||
/**
|
||||
* __nla_put_nohdr - Add a netlink attribute without header
|
||||
* @skb: socket buffer to add attribute to
|
||||
* @attrlen: length of attribute payload
|
||||
* @data: head of attribute payload
|
||||
*
|
||||
* The caller is responsible to ensure that the skb provides enough
|
||||
* tailroom for the attribute payload.
|
||||
*/
|
||||
void __nla_put_nohdr(struct sk_buff *skb, int attrlen, const void *data)
|
||||
{
|
||||
void *start;
|
||||
|
||||
start = __nla_reserve_nohdr(skb, attrlen);
|
||||
memcpy(start, data, attrlen);
|
||||
}
|
||||
|
||||
/**
|
||||
* nla_put - Add a netlink attribute to a socket buffer
|
||||
* @skb: socket buffer to add attribute to
|
||||
* @attrtype: attribute type
|
||||
* @attrlen: length of attribute payload
|
||||
* @data: head of attribute payload
|
||||
*
|
||||
* Returns -1 if the tailroom of the skb is insufficient to store
|
||||
* the attribute header and payload.
|
||||
*/
|
||||
int nla_put(struct sk_buff *skb, int attrtype, int attrlen, const void *data)
|
||||
{
|
||||
if (unlikely(skb_tailroom(skb) < nla_total_size(attrlen)))
|
||||
return -1;
|
||||
|
||||
__nla_put(skb, attrtype, attrlen, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* nla_put_nohdr - Add a netlink attribute without header
|
||||
* @skb: socket buffer to add attribute to
|
||||
* @attrlen: length of attribute payload
|
||||
* @data: head of attribute payload
|
||||
*
|
||||
* Returns -1 if the tailroom of the skb is insufficient to store
|
||||
* the attribute payload.
|
||||
*/
|
||||
int nla_put_nohdr(struct sk_buff *skb, int attrlen, const void *data)
|
||||
{
|
||||
if (unlikely(skb_tailroom(skb) < NLA_ALIGN(attrlen)))
|
||||
return -1;
|
||||
|
||||
__nla_put_nohdr(skb, attrlen, data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXPORT_SYMBOL(nla_validate);
|
||||
EXPORT_SYMBOL(nla_parse);
|
||||
EXPORT_SYMBOL(nla_find);
|
||||
EXPORT_SYMBOL(nla_strlcpy);
|
||||
EXPORT_SYMBOL(__nla_reserve);
|
||||
EXPORT_SYMBOL(__nla_reserve_nohdr);
|
||||
EXPORT_SYMBOL(nla_reserve);
|
||||
EXPORT_SYMBOL(nla_reserve_nohdr);
|
||||
EXPORT_SYMBOL(__nla_put);
|
||||
EXPORT_SYMBOL(__nla_put_nohdr);
|
||||
EXPORT_SYMBOL(nla_put);
|
||||
EXPORT_SYMBOL(nla_put_nohdr);
|
||||
EXPORT_SYMBOL(nla_memcpy);
|
||||
EXPORT_SYMBOL(nla_memcmp);
|
||||
EXPORT_SYMBOL(nla_strcmp);
|
||||
607
net/netlink/genetlink.c
Normal file
607
net/netlink/genetlink.c
Normal file
@@ -0,0 +1,607 @@
|
||||
/*
|
||||
* NETLINK Generic Netlink Family
|
||||
*
|
||||
* Authors: Jamal Hadi Salim
|
||||
* Thomas Graf <tgraf@suug.ch>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/socket.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/skbuff.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <net/sock.h>
|
||||
#include <net/genetlink.h>
|
||||
|
||||
struct sock *genl_sock = NULL;
|
||||
|
||||
static DEFINE_MUTEX(genl_mutex); /* serialization of message processing */
|
||||
|
||||
static void genl_lock(void)
|
||||
{
|
||||
mutex_lock(&genl_mutex);
|
||||
}
|
||||
|
||||
static int genl_trylock(void)
|
||||
{
|
||||
return !mutex_trylock(&genl_mutex);
|
||||
}
|
||||
|
||||
static void genl_unlock(void)
|
||||
{
|
||||
mutex_unlock(&genl_mutex);
|
||||
|
||||
if (genl_sock && genl_sock->sk_receive_queue.qlen)
|
||||
genl_sock->sk_data_ready(genl_sock, 0);
|
||||
}
|
||||
|
||||
#define GENL_FAM_TAB_SIZE 16
|
||||
#define GENL_FAM_TAB_MASK (GENL_FAM_TAB_SIZE - 1)
|
||||
|
||||
static struct list_head family_ht[GENL_FAM_TAB_SIZE];
|
||||
|
||||
static int genl_ctrl_event(int event, void *data);
|
||||
|
||||
static inline unsigned int genl_family_hash(unsigned int id)
|
||||
{
|
||||
return id & GENL_FAM_TAB_MASK;
|
||||
}
|
||||
|
||||
static inline struct list_head *genl_family_chain(unsigned int id)
|
||||
{
|
||||
return &family_ht[genl_family_hash(id)];
|
||||
}
|
||||
|
||||
static struct genl_family *genl_family_find_byid(unsigned int id)
|
||||
{
|
||||
struct genl_family *f;
|
||||
|
||||
list_for_each_entry(f, genl_family_chain(id), family_list)
|
||||
if (f->id == id)
|
||||
return f;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct genl_family *genl_family_find_byname(char *name)
|
||||
{
|
||||
struct genl_family *f;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < GENL_FAM_TAB_SIZE; i++)
|
||||
list_for_each_entry(f, genl_family_chain(i), family_list)
|
||||
if (strcmp(f->name, name) == 0)
|
||||
return f;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static struct genl_ops *genl_get_cmd(u8 cmd, struct genl_family *family)
|
||||
{
|
||||
struct genl_ops *ops;
|
||||
|
||||
list_for_each_entry(ops, &family->ops_list, ops_list)
|
||||
if (ops->cmd == cmd)
|
||||
return ops;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Of course we are going to have problems once we hit
|
||||
* 2^16 alive types, but that can only happen by year 2K
|
||||
*/
|
||||
static inline u16 genl_generate_id(void)
|
||||
{
|
||||
static u16 id_gen_idx;
|
||||
int overflowed = 0;
|
||||
|
||||
do {
|
||||
if (id_gen_idx == 0)
|
||||
id_gen_idx = GENL_MIN_ID;
|
||||
|
||||
if (++id_gen_idx > GENL_MAX_ID) {
|
||||
if (!overflowed) {
|
||||
overflowed = 1;
|
||||
id_gen_idx = 0;
|
||||
continue;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
} while (genl_family_find_byid(id_gen_idx));
|
||||
|
||||
return id_gen_idx;
|
||||
}
|
||||
|
||||
/**
|
||||
* genl_register_ops - register generic netlink operations
|
||||
* @family: generic netlink family
|
||||
* @ops: operations to be registered
|
||||
*
|
||||
* Registers the specified operations and assigns them to the specified
|
||||
* family. Either a doit or dumpit callback must be specified or the
|
||||
* operation will fail. Only one operation structure per command
|
||||
* identifier may be registered.
|
||||
*
|
||||
* See include/net/genetlink.h for more documenation on the operations
|
||||
* structure.
|
||||
*
|
||||
* Returns 0 on success or a negative error code.
|
||||
*/
|
||||
int genl_register_ops(struct genl_family *family, struct genl_ops *ops)
|
||||
{
|
||||
int err = -EINVAL;
|
||||
|
||||
if (ops->dumpit == NULL && ops->doit == NULL)
|
||||
goto errout;
|
||||
|
||||
if (genl_get_cmd(ops->cmd, family)) {
|
||||
err = -EEXIST;
|
||||
goto errout;
|
||||
}
|
||||
|
||||
if (ops->dumpit)
|
||||
ops->flags |= GENL_CMD_CAP_DUMP;
|
||||
if (ops->doit)
|
||||
ops->flags |= GENL_CMD_CAP_DO;
|
||||
if (ops->policy)
|
||||
ops->flags |= GENL_CMD_CAP_HASPOL;
|
||||
|
||||
genl_lock();
|
||||
list_add_tail(&ops->ops_list, &family->ops_list);
|
||||
genl_unlock();
|
||||
|
||||
genl_ctrl_event(CTRL_CMD_NEWOPS, ops);
|
||||
err = 0;
|
||||
errout:
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* genl_unregister_ops - unregister generic netlink operations
|
||||
* @family: generic netlink family
|
||||
* @ops: operations to be unregistered
|
||||
*
|
||||
* Unregisters the specified operations and unassigns them from the
|
||||
* specified family. The operation blocks until the current message
|
||||
* processing has finished and doesn't start again until the
|
||||
* unregister process has finished.
|
||||
*
|
||||
* Note: It is not necessary to unregister all operations before
|
||||
* unregistering the family, unregistering the family will cause
|
||||
* all assigned operations to be unregistered automatically.
|
||||
*
|
||||
* Returns 0 on success or a negative error code.
|
||||
*/
|
||||
int genl_unregister_ops(struct genl_family *family, struct genl_ops *ops)
|
||||
{
|
||||
struct genl_ops *rc;
|
||||
|
||||
genl_lock();
|
||||
list_for_each_entry(rc, &family->ops_list, ops_list) {
|
||||
if (rc == ops) {
|
||||
list_del(&ops->ops_list);
|
||||
genl_unlock();
|
||||
genl_ctrl_event(CTRL_CMD_DELOPS, ops);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
genl_unlock();
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* genl_register_family - register a generic netlink family
|
||||
* @family: generic netlink family
|
||||
*
|
||||
* Registers the specified family after validating it first. Only one
|
||||
* family may be registered with the same family name or identifier.
|
||||
* The family id may equal GENL_ID_GENERATE causing an unique id to
|
||||
* be automatically generated and assigned.
|
||||
*
|
||||
* Return 0 on success or a negative error code.
|
||||
*/
|
||||
int genl_register_family(struct genl_family *family)
|
||||
{
|
||||
int err = -EINVAL;
|
||||
|
||||
if (family->id && family->id < GENL_MIN_ID)
|
||||
goto errout;
|
||||
|
||||
if (family->id > GENL_MAX_ID)
|
||||
goto errout;
|
||||
|
||||
INIT_LIST_HEAD(&family->ops_list);
|
||||
|
||||
genl_lock();
|
||||
|
||||
if (genl_family_find_byname(family->name)) {
|
||||
err = -EEXIST;
|
||||
goto errout_locked;
|
||||
}
|
||||
|
||||
if (genl_family_find_byid(family->id)) {
|
||||
err = -EEXIST;
|
||||
goto errout_locked;
|
||||
}
|
||||
|
||||
if (family->id == GENL_ID_GENERATE) {
|
||||
u16 newid = genl_generate_id();
|
||||
|
||||
if (!newid) {
|
||||
err = -ENOMEM;
|
||||
goto errout_locked;
|
||||
}
|
||||
|
||||
family->id = newid;
|
||||
}
|
||||
|
||||
if (family->maxattr) {
|
||||
family->attrbuf = kmalloc((family->maxattr+1) *
|
||||
sizeof(struct nlattr *), GFP_KERNEL);
|
||||
if (family->attrbuf == NULL) {
|
||||
err = -ENOMEM;
|
||||
goto errout_locked;
|
||||
}
|
||||
} else
|
||||
family->attrbuf = NULL;
|
||||
|
||||
list_add_tail(&family->family_list, genl_family_chain(family->id));
|
||||
genl_unlock();
|
||||
|
||||
genl_ctrl_event(CTRL_CMD_NEWFAMILY, family);
|
||||
|
||||
return 0;
|
||||
|
||||
errout_locked:
|
||||
genl_unlock();
|
||||
errout:
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* genl_unregister_family - unregister generic netlink family
|
||||
* @family: generic netlink family
|
||||
*
|
||||
* Unregisters the specified family.
|
||||
*
|
||||
* Returns 0 on success or a negative error code.
|
||||
*/
|
||||
int genl_unregister_family(struct genl_family *family)
|
||||
{
|
||||
struct genl_family *rc;
|
||||
|
||||
genl_lock();
|
||||
|
||||
list_for_each_entry(rc, genl_family_chain(family->id), family_list) {
|
||||
if (family->id != rc->id || strcmp(rc->name, family->name))
|
||||
continue;
|
||||
|
||||
list_del(&rc->family_list);
|
||||
INIT_LIST_HEAD(&family->ops_list);
|
||||
genl_unlock();
|
||||
|
||||
kfree(family->attrbuf);
|
||||
genl_ctrl_event(CTRL_CMD_DELFAMILY, family);
|
||||
return 0;
|
||||
}
|
||||
|
||||
genl_unlock();
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
static int genl_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh,
|
||||
int *errp)
|
||||
{
|
||||
struct genl_ops *ops;
|
||||
struct genl_family *family;
|
||||
struct genl_info info;
|
||||
struct genlmsghdr *hdr = nlmsg_data(nlh);
|
||||
int hdrlen, err = -EINVAL;
|
||||
|
||||
if (!(nlh->nlmsg_flags & NLM_F_REQUEST))
|
||||
goto ignore;
|
||||
|
||||
if (nlh->nlmsg_type < NLMSG_MIN_TYPE)
|
||||
goto ignore;
|
||||
|
||||
family = genl_family_find_byid(nlh->nlmsg_type);
|
||||
if (family == NULL) {
|
||||
err = -ENOENT;
|
||||
goto errout;
|
||||
}
|
||||
|
||||
hdrlen = GENL_HDRLEN + family->hdrsize;
|
||||
if (nlh->nlmsg_len < nlmsg_msg_size(hdrlen))
|
||||
goto errout;
|
||||
|
||||
ops = genl_get_cmd(hdr->cmd, family);
|
||||
if (ops == NULL) {
|
||||
err = -EOPNOTSUPP;
|
||||
goto errout;
|
||||
}
|
||||
|
||||
if ((ops->flags & GENL_ADMIN_PERM) && security_netlink_recv(skb, CAP_NET_ADMIN)) {
|
||||
err = -EPERM;
|
||||
goto errout;
|
||||
}
|
||||
|
||||
if (nlh->nlmsg_flags & NLM_F_DUMP) {
|
||||
if (ops->dumpit == NULL) {
|
||||
err = -EOPNOTSUPP;
|
||||
goto errout;
|
||||
}
|
||||
|
||||
*errp = err = netlink_dump_start(genl_sock, skb, nlh,
|
||||
ops->dumpit, ops->done);
|
||||
if (err == 0)
|
||||
skb_pull(skb, min(NLMSG_ALIGN(nlh->nlmsg_len),
|
||||
skb->len));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ops->doit == NULL) {
|
||||
err = -EOPNOTSUPP;
|
||||
goto errout;
|
||||
}
|
||||
|
||||
if (family->attrbuf) {
|
||||
err = nlmsg_parse(nlh, hdrlen, family->attrbuf, family->maxattr,
|
||||
ops->policy);
|
||||
if (err < 0)
|
||||
goto errout;
|
||||
}
|
||||
|
||||
info.snd_seq = nlh->nlmsg_seq;
|
||||
info.snd_pid = NETLINK_CB(skb).pid;
|
||||
info.nlhdr = nlh;
|
||||
info.genlhdr = nlmsg_data(nlh);
|
||||
info.userhdr = nlmsg_data(nlh) + GENL_HDRLEN;
|
||||
info.attrs = family->attrbuf;
|
||||
|
||||
*errp = err = ops->doit(skb, &info);
|
||||
return err;
|
||||
|
||||
ignore:
|
||||
return 0;
|
||||
|
||||
errout:
|
||||
*errp = err;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void genl_rcv(struct sock *sk, int len)
|
||||
{
|
||||
unsigned int qlen = 0;
|
||||
|
||||
do {
|
||||
if (genl_trylock())
|
||||
return;
|
||||
netlink_run_queue(sk, &qlen, genl_rcv_msg);
|
||||
genl_unlock();
|
||||
} while (qlen && genl_sock && genl_sock->sk_receive_queue.qlen);
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
* Controller
|
||||
**************************************************************************/
|
||||
|
||||
static struct genl_family genl_ctrl = {
|
||||
.id = GENL_ID_CTRL,
|
||||
.name = "nlctrl",
|
||||
.version = 0x2,
|
||||
.maxattr = CTRL_ATTR_MAX,
|
||||
};
|
||||
|
||||
static int ctrl_fill_info(struct genl_family *family, u32 pid, u32 seq,
|
||||
u32 flags, struct sk_buff *skb, u8 cmd)
|
||||
{
|
||||
void *hdr;
|
||||
|
||||
hdr = genlmsg_put(skb, pid, seq, &genl_ctrl, flags, cmd);
|
||||
if (hdr == NULL)
|
||||
return -1;
|
||||
|
||||
NLA_PUT_STRING(skb, CTRL_ATTR_FAMILY_NAME, family->name);
|
||||
NLA_PUT_U16(skb, CTRL_ATTR_FAMILY_ID, family->id);
|
||||
NLA_PUT_U32(skb, CTRL_ATTR_VERSION, family->version);
|
||||
NLA_PUT_U32(skb, CTRL_ATTR_HDRSIZE, family->hdrsize);
|
||||
NLA_PUT_U32(skb, CTRL_ATTR_MAXATTR, family->maxattr);
|
||||
|
||||
if (!list_empty(&family->ops_list)) {
|
||||
struct nlattr *nla_ops;
|
||||
struct genl_ops *ops;
|
||||
int idx = 1;
|
||||
|
||||
nla_ops = nla_nest_start(skb, CTRL_ATTR_OPS);
|
||||
if (nla_ops == NULL)
|
||||
goto nla_put_failure;
|
||||
|
||||
list_for_each_entry(ops, &family->ops_list, ops_list) {
|
||||
struct nlattr *nest;
|
||||
|
||||
nest = nla_nest_start(skb, idx++);
|
||||
if (nest == NULL)
|
||||
goto nla_put_failure;
|
||||
|
||||
NLA_PUT_U32(skb, CTRL_ATTR_OP_ID, ops->cmd);
|
||||
NLA_PUT_U32(skb, CTRL_ATTR_OP_FLAGS, ops->flags);
|
||||
|
||||
nla_nest_end(skb, nest);
|
||||
}
|
||||
|
||||
nla_nest_end(skb, nla_ops);
|
||||
}
|
||||
|
||||
return genlmsg_end(skb, hdr);
|
||||
|
||||
nla_put_failure:
|
||||
return genlmsg_cancel(skb, hdr);
|
||||
}
|
||||
|
||||
static int ctrl_dumpfamily(struct sk_buff *skb, struct netlink_callback *cb)
|
||||
{
|
||||
|
||||
int i, n = 0;
|
||||
struct genl_family *rt;
|
||||
int chains_to_skip = cb->args[0];
|
||||
int fams_to_skip = cb->args[1];
|
||||
|
||||
if (chains_to_skip != 0)
|
||||
genl_lock();
|
||||
|
||||
for (i = 0; i < GENL_FAM_TAB_SIZE; i++) {
|
||||
if (i < chains_to_skip)
|
||||
continue;
|
||||
n = 0;
|
||||
list_for_each_entry(rt, genl_family_chain(i), family_list) {
|
||||
if (++n < fams_to_skip)
|
||||
continue;
|
||||
if (ctrl_fill_info(rt, NETLINK_CB(cb->skb).pid,
|
||||
cb->nlh->nlmsg_seq, NLM_F_MULTI,
|
||||
skb, CTRL_CMD_NEWFAMILY) < 0)
|
||||
goto errout;
|
||||
}
|
||||
|
||||
fams_to_skip = 0;
|
||||
}
|
||||
|
||||
errout:
|
||||
if (chains_to_skip != 0)
|
||||
genl_unlock();
|
||||
|
||||
cb->args[0] = i;
|
||||
cb->args[1] = n;
|
||||
|
||||
return skb->len;
|
||||
}
|
||||
|
||||
static struct sk_buff *ctrl_build_msg(struct genl_family *family, u32 pid,
|
||||
int seq, u8 cmd)
|
||||
{
|
||||
struct sk_buff *skb;
|
||||
int err;
|
||||
|
||||
skb = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
|
||||
if (skb == NULL)
|
||||
return ERR_PTR(-ENOBUFS);
|
||||
|
||||
err = ctrl_fill_info(family, pid, seq, 0, skb, cmd);
|
||||
if (err < 0) {
|
||||
nlmsg_free(skb);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
return skb;
|
||||
}
|
||||
|
||||
static struct nla_policy ctrl_policy[CTRL_ATTR_MAX+1] __read_mostly = {
|
||||
[CTRL_ATTR_FAMILY_ID] = { .type = NLA_U16 },
|
||||
[CTRL_ATTR_FAMILY_NAME] = { .type = NLA_NUL_STRING,
|
||||
.len = GENL_NAMSIZ - 1 },
|
||||
};
|
||||
|
||||
static int ctrl_getfamily(struct sk_buff *skb, struct genl_info *info)
|
||||
{
|
||||
struct sk_buff *msg;
|
||||
struct genl_family *res = NULL;
|
||||
int err = -EINVAL;
|
||||
|
||||
if (info->attrs[CTRL_ATTR_FAMILY_ID]) {
|
||||
u16 id = nla_get_u16(info->attrs[CTRL_ATTR_FAMILY_ID]);
|
||||
res = genl_family_find_byid(id);
|
||||
}
|
||||
|
||||
if (info->attrs[CTRL_ATTR_FAMILY_NAME]) {
|
||||
char *name;
|
||||
|
||||
name = nla_data(info->attrs[CTRL_ATTR_FAMILY_NAME]);
|
||||
res = genl_family_find_byname(name);
|
||||
}
|
||||
|
||||
if (res == NULL) {
|
||||
err = -ENOENT;
|
||||
goto errout;
|
||||
}
|
||||
|
||||
msg = ctrl_build_msg(res, info->snd_pid, info->snd_seq,
|
||||
CTRL_CMD_NEWFAMILY);
|
||||
if (IS_ERR(msg)) {
|
||||
err = PTR_ERR(msg);
|
||||
goto errout;
|
||||
}
|
||||
|
||||
err = genlmsg_reply(msg, info);
|
||||
errout:
|
||||
return err;
|
||||
}
|
||||
|
||||
static int genl_ctrl_event(int event, void *data)
|
||||
{
|
||||
struct sk_buff *msg;
|
||||
|
||||
if (genl_sock == NULL)
|
||||
return 0;
|
||||
|
||||
switch (event) {
|
||||
case CTRL_CMD_NEWFAMILY:
|
||||
case CTRL_CMD_DELFAMILY:
|
||||
msg = ctrl_build_msg(data, 0, 0, event);
|
||||
if (IS_ERR(msg))
|
||||
return PTR_ERR(msg);
|
||||
|
||||
genlmsg_multicast(msg, 0, GENL_ID_CTRL, GFP_KERNEL);
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct genl_ops genl_ctrl_ops = {
|
||||
.cmd = CTRL_CMD_GETFAMILY,
|
||||
.doit = ctrl_getfamily,
|
||||
.dumpit = ctrl_dumpfamily,
|
||||
.policy = ctrl_policy,
|
||||
};
|
||||
|
||||
static int __init genl_init(void)
|
||||
{
|
||||
int i, err;
|
||||
|
||||
for (i = 0; i < GENL_FAM_TAB_SIZE; i++)
|
||||
INIT_LIST_HEAD(&family_ht[i]);
|
||||
|
||||
err = genl_register_family(&genl_ctrl);
|
||||
if (err < 0)
|
||||
goto errout;
|
||||
|
||||
err = genl_register_ops(&genl_ctrl, &genl_ctrl_ops);
|
||||
if (err < 0)
|
||||
goto errout_register;
|
||||
|
||||
netlink_set_nonroot(NETLINK_GENERIC, NL_NONROOT_RECV);
|
||||
genl_sock = netlink_kernel_create(NETLINK_GENERIC, GENL_MAX_ID,
|
||||
genl_rcv, THIS_MODULE);
|
||||
if (genl_sock == NULL)
|
||||
panic("GENL: Cannot initialize generic netlink\n");
|
||||
|
||||
return 0;
|
||||
|
||||
errout_register:
|
||||
genl_unregister_family(&genl_ctrl);
|
||||
errout:
|
||||
panic("GENL: Cannot register controller: %d\n", err);
|
||||
}
|
||||
|
||||
subsys_initcall(genl_init);
|
||||
|
||||
EXPORT_SYMBOL(genl_sock);
|
||||
EXPORT_SYMBOL(genl_register_ops);
|
||||
EXPORT_SYMBOL(genl_unregister_ops);
|
||||
EXPORT_SYMBOL(genl_register_family);
|
||||
EXPORT_SYMBOL(genl_unregister_family);
|
||||
Reference in New Issue
Block a user