2005-11-10 04:25:56 +03:00
/*
* 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>
2006-03-26 13:37:14 +04:00
# include <linux/mutex.h>
2005-11-10 04:25:56 +03:00
# include <net/sock.h>
# include <net/genetlink.h>
struct sock * genl_sock = NULL ;
2006-03-26 13:37:14 +04:00
static DEFINE_MUTEX ( genl_mutex ) ; /* serialization of message processing */
2005-11-10 04:25:56 +03:00
static void genl_lock ( void )
{
2006-03-26 13:37:14 +04:00
mutex_lock ( & genl_mutex ) ;
2005-11-10 04:25:56 +03:00
}
static int genl_trylock ( void )
{
2006-03-26 13:37:14 +04:00
return ! mutex_trylock ( & genl_mutex ) ;
2005-11-10 04:25:56 +03:00
}
static void genl_unlock ( void )
{
2006-03-26 13:37:14 +04:00
mutex_unlock ( & genl_mutex ) ;
2005-11-10 04:25:56 +03:00
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 2 K
*/
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 ;
}
2006-12-05 06:31:51 +03:00
if ( ops - > dumpit )
ops - > flags | = GENL_CMD_CAP_DO ;
if ( ops - > doit )
ops - > flags | = GENL_CMD_CAP_DUMP ;
if ( ops - > policy )
ops - > flags | = GENL_CMD_CAP_HASPOL ;
2005-11-10 04:25:56 +03:00
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 ;
2006-02-14 02:51:24 +03:00
goto errout_locked ;
2005-11-10 04:25:56 +03:00
}
} 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 ;
}
2006-02-14 02:51:24 +03:00
static int genl_rcv_msg ( struct sk_buff * skb , struct nlmsghdr * nlh ,
2005-11-10 04:25:56 +03:00
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 ;
}
2006-06-28 00:26:11 +04:00
if ( ( ops - > flags & GENL_ADMIN_PERM ) & & security_netlink_recv ( skb , CAP_NET_ADMIN ) ) {
2005-11-10 04:25:56 +03:00
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 ,
2006-12-02 07:07:42 +03:00
ops - > dumpit , ops - > done ) ;
2005-11-10 04:25:56 +03:00
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 ;
2006-02-14 02:51:24 +03:00
netlink_run_queue ( sk , & qlen , genl_rcv_msg ) ;
2005-11-10 04:25:56 +03:00
genl_unlock ( ) ;
} while ( qlen & & genl_sock & & genl_sock - > sk_receive_queue . qlen ) ;
}
/**************************************************************************
* Controller
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2006-11-15 06:46:02 +03:00
static struct genl_family genl_ctrl = {
. id = GENL_ID_CTRL ,
. name = " nlctrl " ,
2006-12-05 06:31:51 +03:00
. version = 0x2 ,
2006-11-15 06:46:02 +03:00
. maxattr = CTRL_ATTR_MAX ,
} ;
2005-11-10 04:25:56 +03:00
static int ctrl_fill_info ( struct genl_family * family , u32 pid , u32 seq ,
u32 flags , struct sk_buff * skb , u8 cmd )
{
void * hdr ;
2006-11-15 06:46:02 +03:00
hdr = genlmsg_put ( skb , pid , seq , & genl_ctrl , flags , cmd ) ;
2005-11-10 04:25:56 +03:00
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 ) ;
2006-09-18 11:01:59 +04:00
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 ) ;
2006-11-23 22:44:37 +03:00
if ( ! list_empty ( & family - > ops_list ) ) {
struct nlattr * nla_ops ;
struct genl_ops * ops ;
int idx = 1 ;
2006-09-18 11:01:59 +04:00
2006-11-23 22:44:37 +03:00
nla_ops = nla_nest_start ( skb , CTRL_ATTR_OPS ) ;
if ( nla_ops = = NULL )
2006-09-18 11:01:59 +04:00
goto nla_put_failure ;
2006-11-23 22:44:37 +03:00
list_for_each_entry ( ops , & family - > ops_list , ops_list ) {
struct nlattr * nest ;
2006-09-18 11:01:59 +04:00
2006-11-23 22:44:37 +03:00
nest = nla_nest_start ( skb , idx + + ) ;
if ( nest = = NULL )
goto nla_put_failure ;
2006-09-18 11:01:59 +04:00
2006-11-23 22:44:37 +03:00
NLA_PUT_U32 ( skb , CTRL_ATTR_OP_ID , ops - > cmd ) ;
NLA_PUT_U32 ( skb , CTRL_ATTR_OP_FLAGS , ops - > flags ) ;
2006-09-18 11:01:59 +04:00
2006-11-23 22:44:37 +03:00
nla_nest_end ( skb , nest ) ;
}
nla_nest_end ( skb , nla_ops ) ;
}
2005-11-10 04:25:56 +03:00
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 ] ;
2006-09-18 11:01:59 +04:00
if ( chains_to_skip ! = 0 )
genl_lock ( ) ;
2005-11-10 04:25:56 +03:00
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 :
2006-09-18 11:01:59 +04:00
if ( chains_to_skip ! = 0 )
genl_unlock ( ) ;
2005-11-10 04:25:56 +03:00
cb - > args [ 0 ] = i ;
cb - > args [ 1 ] = n ;
return skb - > len ;
}
static struct sk_buff * ctrl_build_msg ( struct genl_family * family , u32 pid ,
2006-01-04 01:13:29 +03:00
int seq , u8 cmd )
2005-11-10 04:25:56 +03:00
{
struct sk_buff * skb ;
int err ;
2006-11-11 01:10:15 +03:00
skb = nlmsg_new ( NLMSG_DEFAULT_SIZE , GFP_KERNEL ) ;
2005-11-10 04:25:56 +03:00
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 } ,
2006-08-27 07:13:18 +04:00
[ CTRL_ATTR_FAMILY_NAME ] = { . type = NLA_NUL_STRING ,
. len = GENL_NAMSIZ - 1 } ,
2005-11-10 04:25:56 +03:00
} ;
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 ] ) {
2006-08-27 07:13:18 +04:00
char * name ;
2005-11-10 04:25:56 +03:00
2006-08-27 07:13:18 +04:00
name = nla_data ( info - > attrs [ CTRL_ATTR_FAMILY_NAME ] ) ;
2005-11-10 04:25:56 +03:00
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 ;
}
2006-11-15 06:45:27 +03:00
err = genlmsg_reply ( msg , info ) ;
2005-11-10 04:25:56 +03:00
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 ) ;
2006-08-15 11:31:06 +04:00
genlmsg_multicast ( msg , 0 , GENL_ID_CTRL , GFP_KERNEL ) ;
2005-11-10 04:25:56 +03:00
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 ) ;
2006-02-14 02:51:24 +03:00
if ( genl_sock = = NULL )
2005-11-10 04:25:56 +03:00
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 ) ;