2005-08-09 19:30:24 -07:00
/* Netfilter messages via netlink socket. Allows for user space
* protocol helpers and general trouble making from userspace .
*
* ( C ) 2001 by Jay Schulist < jschlst @ samba . org > ,
* ( C ) 2002 - 2005 by Harald Welte < laforge @ gnumonks . org >
* ( C ) 2005 by Pablo Neira Ayuso < pablo @ eurodev . net >
*
* Initial netfilter messages via netlink development funded and
* generally made possible by Network Robots , Inc . ( www . networkrobots . com )
*
* Further development of this code funded by Astaro AG ( http : //www.astaro.com)
*
* This software may be used and distributed according to the terms
* of the GNU General Public License , incorporated herein by reference .
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/socket.h>
# include <linux/kernel.h>
# include <linux/major.h>
# include <linux/sched.h>
# include <linux/timer.h>
# include <linux/string.h>
# include <linux/sockios.h>
# include <linux/net.h>
# include <linux/fcntl.h>
# include <linux/skbuff.h>
# include <asm/uaccess.h>
# include <asm/system.h>
# include <net/sock.h>
# include <linux/init.h>
# include <linux/spinlock.h>
# include <linux/netfilter.h>
# include <linux/netlink.h>
# include <linux/netfilter/nfnetlink.h>
MODULE_LICENSE ( " GPL " ) ;
2005-08-09 19:40:55 -07:00
MODULE_AUTHOR ( " Harald Welte <laforge@netfilter.org> " ) ;
MODULE_ALIAS_NET_PF_PROTO ( PF_NETLINK , NETLINK_NETFILTER ) ;
2005-08-09 19:30:24 -07:00
static char __initdata nfversion [ ] = " 0.30 " ;
#if 0
2005-08-09 19:43:44 -07:00
# define DEBUGP(format, args...) \
printk ( KERN_DEBUG " %s(%d):%s(): " format , __FILE__ , \
__LINE__ , __FUNCTION__ , # # args )
2005-08-09 19:30:24 -07:00
# else
# define DEBUGP(format, args...)
# endif
static struct sock * nfnl = NULL ;
static struct nfnetlink_subsystem * subsys_table [ NFNL_SUBSYS_COUNT ] ;
DECLARE_MUTEX ( nfnl_sem ) ;
void nfnl_lock ( void )
{
nfnl_shlock ( ) ;
}
void nfnl_unlock ( void )
{
nfnl_shunlock ( ) ;
}
int nfnetlink_subsys_register ( struct nfnetlink_subsystem * n )
{
DEBUGP ( " registering subsystem ID %u \n " , n - > subsys_id ) ;
nfnl_lock ( ) ;
2005-08-09 19:43:44 -07:00
if ( subsys_table [ n - > subsys_id ] ) {
nfnl_unlock ( ) ;
return - EBUSY ;
}
2005-08-09 19:30:24 -07:00
subsys_table [ n - > subsys_id ] = n ;
nfnl_unlock ( ) ;
return 0 ;
}
int nfnetlink_subsys_unregister ( struct nfnetlink_subsystem * n )
{
DEBUGP ( " unregistering subsystem ID %u \n " , n - > subsys_id ) ;
nfnl_lock ( ) ;
subsys_table [ n - > subsys_id ] = NULL ;
nfnl_unlock ( ) ;
return 0 ;
}
static inline struct nfnetlink_subsystem * nfnetlink_get_subsys ( u_int16_t type )
{
u_int8_t subsys_id = NFNL_SUBSYS_ID ( type ) ;
if ( subsys_id > = NFNL_SUBSYS_COUNT
| | subsys_table [ subsys_id ] = = NULL )
return NULL ;
return subsys_table [ subsys_id ] ;
}
static inline struct nfnl_callback *
nfnetlink_find_client ( u_int16_t type , struct nfnetlink_subsystem * ss )
{
u_int8_t cb_id = NFNL_MSG_TYPE ( type ) ;
if ( cb_id > = ss - > cb_count ) {
DEBUGP ( " msgtype %u >= %u, returning \n " , type , ss - > cb_count ) ;
return NULL ;
}
return & ss - > cb [ cb_id ] ;
}
void __nfa_fill ( struct sk_buff * skb , int attrtype , int attrlen ,
const void * data )
{
struct nfattr * nfa ;
int size = NFA_LENGTH ( attrlen ) ;
nfa = ( struct nfattr * ) skb_put ( skb , NFA_ALIGN ( size ) ) ;
nfa - > nfa_type = attrtype ;
nfa - > nfa_len = size ;
memcpy ( NFA_DATA ( nfa ) , data , attrlen ) ;
2005-08-09 19:32:58 -07:00
memset ( NFA_DATA ( nfa ) + attrlen , 0 , NFA_ALIGN ( size ) - size ) ;
2005-08-09 19:30:24 -07:00
}
2005-11-09 12:59:13 -08:00
void nfattr_parse ( struct nfattr * tb [ ] , int maxattr , struct nfattr * nfa , int len )
2005-08-09 19:30:24 -07:00
{
memset ( tb , 0 , sizeof ( struct nfattr * ) * maxattr ) ;
while ( NFA_OK ( nfa , len ) ) {
2005-10-10 20:52:19 -07:00
unsigned flavor = NFA_TYPE ( nfa ) ;
2005-08-09 19:30:24 -07:00
if ( flavor & & flavor < = maxattr )
tb [ flavor - 1 ] = nfa ;
nfa = NFA_NEXT ( nfa , len ) ;
}
}
/**
* nfnetlink_check_attributes - check and parse nfnetlink attributes
*
* subsys : nfnl subsystem for which this message is to be parsed
* nlmsghdr : netlink message to be checked / parsed
* cda : array of pointers , needs to be at least subsys - > attr_count big
*
*/
static int
nfnetlink_check_attributes ( struct nfnetlink_subsystem * subsys ,
struct nlmsghdr * nlh , struct nfattr * cda [ ] )
{
int min_len ;
2005-08-09 20:03:40 -07:00
u_int16_t attr_count ;
u_int8_t cb_id = NFNL_MSG_TYPE ( nlh - > nlmsg_type ) ;
2005-08-09 19:30:24 -07:00
2005-08-09 20:03:40 -07:00
if ( unlikely ( cb_id > = subsys - > cb_count ) ) {
DEBUGP ( " msgtype %u >= %u, returning \n " ,
cb_id , subsys - > cb_count ) ;
return - EINVAL ;
}
2005-08-09 19:30:24 -07:00
2005-12-05 13:33:26 -08:00
min_len = NLMSG_SPACE ( sizeof ( struct nfgenmsg ) ) ;
2005-08-09 20:03:54 -07:00
if ( unlikely ( nlh - > nlmsg_len < min_len ) )
2005-08-09 19:30:24 -07:00
return - EINVAL ;
2005-08-09 20:03:54 -07:00
attr_count = subsys - > cb [ cb_id ] . attr_count ;
memset ( cda , 0 , sizeof ( struct nfattr * ) * attr_count ) ;
/* check attribute lengths. */
if ( likely ( nlh - > nlmsg_len > min_len ) ) {
2005-08-09 19:30:24 -07:00
struct nfattr * attr = NFM_NFA ( NLMSG_DATA ( nlh ) ) ;
int attrlen = nlh - > nlmsg_len - NLMSG_ALIGN ( min_len ) ;
while ( NFA_OK ( attr , attrlen ) ) {
2005-10-10 20:52:19 -07:00
unsigned flavor = NFA_TYPE ( attr ) ;
2005-08-09 19:30:24 -07:00
if ( flavor ) {
2005-08-09 20:03:40 -07:00
if ( flavor > attr_count )
2005-08-09 19:30:24 -07:00
return - EINVAL ;
cda [ flavor - 1 ] = attr ;
}
attr = NFA_NEXT ( attr , attrlen ) ;
}
2005-08-09 20:03:54 -07:00
}
/* implicit: if nlmsg_len == min_len, we return 0, and an empty
* ( zeroed ) cda [ ] array . The message is valid , but empty . */
2005-08-09 19:30:24 -07:00
return 0 ;
}
2006-03-20 18:03:59 -08:00
int nfnetlink_has_listeners ( unsigned int group )
{
return netlink_has_listeners ( nfnl , group ) ;
}
EXPORT_SYMBOL_GPL ( nfnetlink_has_listeners ) ;
2005-08-09 19:30:24 -07:00
int nfnetlink_send ( struct sk_buff * skb , u32 pid , unsigned group , int echo )
{
2005-10-07 07:46:04 +01:00
gfp_t allocation = in_interrupt ( ) ? GFP_ATOMIC : GFP_KERNEL ;
2005-08-09 19:30:24 -07:00
int err = 0 ;
2005-08-14 19:29:52 -07:00
NETLINK_CB ( skb ) . dst_group = group ;
2005-08-09 19:30:24 -07:00
if ( echo )
atomic_inc ( & skb - > users ) ;
netlink_broadcast ( nfnl , skb , pid , group , allocation ) ;
if ( echo )
err = netlink_unicast ( nfnl , skb , pid , MSG_DONTWAIT ) ;
return err ;
}
int nfnetlink_unicast ( struct sk_buff * skb , u_int32_t pid , int flags )
{
return netlink_unicast ( nfnl , skb , pid , flags ) ;
}
/* Process one complete nfnetlink message. */
2006-01-14 13:20:43 -08:00
static int nfnetlink_rcv_msg ( struct sk_buff * skb ,
2005-08-09 19:30:24 -07:00
struct nlmsghdr * nlh , int * errp )
{
struct nfnl_callback * nc ;
struct nfnetlink_subsystem * ss ;
int type , err = 0 ;
DEBUGP ( " entered; subsys=%u, msgtype=%u \n " ,
NFNL_SUBSYS_ID ( nlh - > nlmsg_type ) ,
NFNL_MSG_TYPE ( nlh - > nlmsg_type ) ) ;
2006-06-27 13:26:11 -07:00
if ( security_netlink_recv ( skb , CAP_NET_ADMIN ) ) {
2005-11-14 15:24:59 -08:00
DEBUGP ( " missing CAP_NET_ADMIN \n " ) ;
* errp = - EPERM ;
return - 1 ;
}
2005-08-09 19:30:24 -07:00
/* Only requests are handled by kernel now. */
if ( ! ( nlh - > nlmsg_flags & NLM_F_REQUEST ) ) {
DEBUGP ( " received non-request message \n " ) ;
return 0 ;
}
/* All the messages must at least contain nfgenmsg */
2005-12-05 13:33:26 -08:00
if ( nlh - > nlmsg_len < NLMSG_SPACE ( sizeof ( struct nfgenmsg ) ) ) {
2005-08-09 19:30:24 -07:00
DEBUGP ( " received message was too short \n " ) ;
return 0 ;
}
type = nlh - > nlmsg_type ;
ss = nfnetlink_get_subsys ( type ) ;
2005-08-09 19:43:44 -07:00
if ( ! ss ) {
# ifdef CONFIG_KMOD
2005-11-14 15:24:59 -08:00
/* don't call nfnl_shunlock, since it would reenter
* with further packet processing */
up ( & nfnl_sem ) ;
request_module ( " nfnetlink-subsys-%d " , NFNL_SUBSYS_ID ( type ) ) ;
nfnl_shlock ( ) ;
ss = nfnetlink_get_subsys ( type ) ;
2005-08-09 19:43:44 -07:00
if ( ! ss )
# endif
2005-11-09 13:02:16 -08:00
goto err_inval ;
2005-08-09 19:43:44 -07:00
}
2005-08-09 19:30:24 -07:00
nc = nfnetlink_find_client ( type , ss ) ;
if ( ! nc ) {
DEBUGP ( " unable to find client for type %d \n " , type ) ;
goto err_inval ;
}
{
2005-08-09 20:03:40 -07:00
u_int16_t attr_count =
ss - > cb [ NFNL_MSG_TYPE ( nlh - > nlmsg_type ) ] . attr_count ;
struct nfattr * cda [ attr_count ] ;
2005-08-09 19:30:24 -07:00
2005-08-09 20:03:40 -07:00
memset ( cda , 0 , sizeof ( struct nfattr * ) * attr_count ) ;
2005-08-09 19:30:24 -07:00
err = nfnetlink_check_attributes ( ss , nlh , cda ) ;
if ( err < 0 )
goto err_inval ;
2005-08-09 19:43:44 -07:00
DEBUGP ( " calling handler \n " ) ;
2005-08-09 19:30:24 -07:00
err = nc - > call ( nfnl , skb , nlh , cda , errp ) ;
* errp = err ;
return err ;
}
err_inval :
2005-08-09 19:43:44 -07:00
DEBUGP ( " returning -EINVAL \n " ) ;
2005-08-09 19:30:24 -07:00
* errp = - EINVAL ;
return - 1 ;
}
/* Process one packet of messages. */
static inline int nfnetlink_rcv_skb ( struct sk_buff * skb )
{
int err ;
struct nlmsghdr * nlh ;
while ( skb - > len > = NLMSG_SPACE ( 0 ) ) {
u32 rlen ;
nlh = ( struct nlmsghdr * ) skb - > data ;
if ( nlh - > nlmsg_len < sizeof ( struct nlmsghdr )
| | skb - > len < nlh - > nlmsg_len )
return 0 ;
rlen = NLMSG_ALIGN ( nlh - > nlmsg_len ) ;
if ( rlen > skb - > len )
rlen = skb - > len ;
if ( nfnetlink_rcv_msg ( skb , nlh , & err ) ) {
if ( ! err )
return - 1 ;
netlink_ack ( skb , nlh , err ) ;
} else
if ( nlh - > nlmsg_flags & NLM_F_ACK )
netlink_ack ( skb , nlh , 0 ) ;
skb_pull ( skb , rlen ) ;
}
return 0 ;
}
static void nfnetlink_rcv ( struct sock * sk , int len )
{
do {
struct sk_buff * skb ;
if ( nfnl_shlock_nowait ( ) )
return ;
while ( ( skb = skb_dequeue ( & sk - > sk_receive_queue ) ) ! = NULL ) {
if ( nfnetlink_rcv_skb ( skb ) ) {
if ( skb - > len )
skb_queue_head ( & sk - > sk_receive_queue ,
skb ) ;
else
kfree_skb ( skb ) ;
break ;
}
kfree_skb ( skb ) ;
}
2005-08-09 19:43:44 -07:00
/* don't call nfnl_shunlock, since it would reenter
* with further packet processing */
2005-08-09 19:30:24 -07:00
up ( & nfnl_sem ) ;
} while ( nfnl & & nfnl - > sk_receive_queue . qlen ) ;
}
2005-09-05 18:06:45 -07:00
static void __exit nfnetlink_exit ( void )
2005-08-09 19:30:24 -07:00
{
printk ( " Removing netfilter NETLINK layer. \n " ) ;
sock_release ( nfnl - > sk_socket ) ;
return ;
}
2005-09-05 18:06:45 -07:00
static int __init nfnetlink_init ( void )
2005-08-09 19:30:24 -07:00
{
printk ( " Netfilter messages via NETLINK v%s. \n " , nfversion ) ;
2005-08-15 12:33:26 -07:00
nfnl = netlink_kernel_create ( NETLINK_NETFILTER , NFNLGRP_MAX ,
nfnetlink_rcv , THIS_MODULE ) ;
2005-08-09 19:30:24 -07:00
if ( ! nfnl ) {
printk ( KERN_ERR " cannot initialize nfnetlink! \n " ) ;
return - 1 ;
}
return 0 ;
}
module_init ( nfnetlink_init ) ;
module_exit ( nfnetlink_exit ) ;
EXPORT_SYMBOL_GPL ( nfnetlink_subsys_register ) ;
EXPORT_SYMBOL_GPL ( nfnetlink_subsys_unregister ) ;
EXPORT_SYMBOL_GPL ( nfnetlink_send ) ;
EXPORT_SYMBOL_GPL ( nfnetlink_unicast ) ;
EXPORT_SYMBOL_GPL ( nfattr_parse ) ;
EXPORT_SYMBOL_GPL ( __nfa_fill ) ;