2005-04-17 02:20:36 +04:00
/*
* net / sched / cls_tcindex . c Packet classifier for skb - > tc_index
*
* Written 1998 , 1999 by Werner Almesberger , EPFL ICA
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/skbuff.h>
# include <linux/errno.h>
# include <net/act_api.h>
2007-03-26 10:06:12 +04:00
# include <net/netlink.h>
2005-04-17 02:20:36 +04:00
# include <net/pkt_cls.h>
/*
* Not quite sure if we need all the xchgs Alexey uses when accessing things .
* Can always add them later . . . : )
*/
/*
* Passing parameters to the root seems to be done more awkwardly than really
* necessary . At least , u32 doesn ' t seem to use such dirty hacks . To be
* verified . FIXME .
*/
# define PERFECT_HASH_THRESHOLD 64 /* use perfect hash if not bigger */
# define DEFAULT_HASH_SIZE 64 /* optimized for diffserv */
# define PRIV(tp) ((struct tcindex_data *) (tp)->root)
struct tcindex_filter_result {
struct tcf_exts exts ;
struct tcf_result res ;
} ;
struct tcindex_filter {
u16 key ;
struct tcindex_filter_result result ;
struct tcindex_filter * next ;
} ;
struct tcindex_data {
struct tcindex_filter_result * perfect ; /* perfect hash; NULL if none */
struct tcindex_filter * * h ; /* imperfect hash; only used if !perfect;
NULL if unused */
u16 mask ; /* AND key with mask */
int shift ; /* shift ANDed key to the right */
int hash ; /* hash table size; 0 if undefined */
int alloc_hash ; /* allocated size */
int fall_through ; /* 0: only classify if explicit match */
} ;
static struct tcf_ext_map tcindex_ext_map = {
. police = TCA_TCINDEX_POLICE ,
. action = TCA_TCINDEX_ACT
} ;
static inline int
tcindex_filter_is_set ( struct tcindex_filter_result * r )
{
return tcf_exts_is_predicative ( & r - > exts ) | | r - > res . classid ;
}
static struct tcindex_filter_result *
tcindex_lookup ( struct tcindex_data * p , u16 key )
{
struct tcindex_filter * f ;
if ( p - > perfect )
return tcindex_filter_is_set ( p - > perfect + key ) ?
p - > perfect + key : NULL ;
else if ( p - > h ) {
for ( f = p - > h [ key % p - > hash ] ; f ; f = f - > next )
if ( f - > key = = key )
return & f - > result ;
}
return NULL ;
}
static int tcindex_classify ( struct sk_buff * skb , struct tcf_proto * tp ,
struct tcf_result * res )
{
struct tcindex_data * p = PRIV ( tp ) ;
struct tcindex_filter_result * f ;
int key = ( skb - > tc_index & p - > mask ) > > p - > shift ;
2008-01-21 13:26:41 +03:00
pr_debug ( " tcindex_classify(skb %p,tp %p,res %p),p %p \n " ,
skb , tp , res , p ) ;
2005-04-17 02:20:36 +04:00
f = tcindex_lookup ( p , key ) ;
if ( ! f ) {
if ( ! p - > fall_through )
return - 1 ;
res - > classid = TC_H_MAKE ( TC_H_MAJ ( tp - > q - > handle ) , key ) ;
res - > class = 0 ;
2008-01-21 13:26:41 +03:00
pr_debug ( " alg 0x%x \n " , res - > classid ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
* res = f - > res ;
2008-01-21 13:26:41 +03:00
pr_debug ( " map 0x%x \n " , res - > classid ) ;
2005-04-17 02:20:36 +04:00
return tcf_exts_exec ( skb , & f - > exts , res ) ;
}
static unsigned long tcindex_get ( struct tcf_proto * tp , u32 handle )
{
struct tcindex_data * p = PRIV ( tp ) ;
struct tcindex_filter_result * r ;
2008-01-21 13:26:41 +03:00
pr_debug ( " tcindex_get(tp %p,handle 0x%08x) \n " , tp , handle ) ;
2005-04-17 02:20:36 +04:00
if ( p - > perfect & & handle > = p - > alloc_hash )
return 0 ;
r = tcindex_lookup ( p , handle ) ;
return r & & tcindex_filter_is_set ( r ) ? ( unsigned long ) r : 0UL ;
}
static void tcindex_put ( struct tcf_proto * tp , unsigned long f )
{
2008-01-21 13:26:41 +03:00
pr_debug ( " tcindex_put(tp %p,f 0x%lx) \n " , tp , f ) ;
2005-04-17 02:20:36 +04:00
}
static int tcindex_init ( struct tcf_proto * tp )
{
struct tcindex_data * p ;
2008-01-21 13:26:41 +03:00
pr_debug ( " tcindex_init(tp %p) \n " , tp ) ;
p = kzalloc ( sizeof ( struct tcindex_data ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! p )
return - ENOMEM ;
p - > mask = 0xffff ;
p - > hash = DEFAULT_HASH_SIZE ;
p - > fall_through = 1 ;
tp - > root = p ;
return 0 ;
}
static int
__tcindex_delete ( struct tcf_proto * tp , unsigned long arg , int lock )
{
struct tcindex_data * p = PRIV ( tp ) ;
struct tcindex_filter_result * r = ( struct tcindex_filter_result * ) arg ;
struct tcindex_filter * f = NULL ;
2008-01-21 13:26:41 +03:00
pr_debug ( " tcindex_delete(tp %p,arg 0x%lx),p %p,f %p \n " , tp , arg , p , f ) ;
2005-04-17 02:20:36 +04:00
if ( p - > perfect ) {
if ( ! r - > res . class )
return - ENOENT ;
} else {
int i ;
struct tcindex_filter * * walk = NULL ;
for ( i = 0 ; i < p - > hash ; i + + )
for ( walk = p - > h + i ; * walk ; walk = & ( * walk ) - > next )
if ( & ( * walk ) - > result = = r )
goto found ;
return - ENOENT ;
found :
f = * walk ;
if ( lock )
tcf_tree_lock ( tp ) ;
* walk = f - > next ;
if ( lock )
tcf_tree_unlock ( tp ) ;
}
tcf_unbind_filter ( tp , & r - > res ) ;
tcf_exts_destroy ( tp , & r - > exts ) ;
2005-11-08 20:41:34 +03:00
kfree ( f ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int tcindex_delete ( struct tcf_proto * tp , unsigned long arg )
{
return __tcindex_delete ( tp , arg , 1 ) ;
}
static inline int
valid_perfect_hash ( struct tcindex_data * p )
{
return p - > hash > ( p - > mask > > p - > shift ) ;
}
static int
tcindex_set_parms ( struct tcf_proto * tp , unsigned long base , u32 handle ,
struct tcindex_data * p , struct tcindex_filter_result * r ,
struct rtattr * * tb , struct rtattr * est )
{
int err , balloc = 0 ;
struct tcindex_filter_result new_filter_result , * old_r = r ;
struct tcindex_filter_result cr ;
struct tcindex_data cp ;
struct tcindex_filter * f = NULL ; /* make gcc behave */
struct tcf_exts e ;
err = tcf_exts_validate ( tp , tb , est , & e , & tcindex_ext_map ) ;
if ( err < 0 )
return err ;
2007-02-09 17:25:16 +03:00
2005-04-17 02:20:36 +04:00
memcpy ( & cp , p , sizeof ( cp ) ) ;
memset ( & new_filter_result , 0 , sizeof ( new_filter_result ) ) ;
if ( old_r )
memcpy ( & cr , r , sizeof ( cr ) ) ;
else
memset ( & cr , 0 , sizeof ( cr ) ) ;
err = - EINVAL ;
if ( tb [ TCA_TCINDEX_HASH - 1 ] ) {
if ( RTA_PAYLOAD ( tb [ TCA_TCINDEX_HASH - 1 ] ) < sizeof ( u32 ) )
goto errout ;
cp . hash = * ( u32 * ) RTA_DATA ( tb [ TCA_TCINDEX_HASH - 1 ] ) ;
}
if ( tb [ TCA_TCINDEX_MASK - 1 ] ) {
if ( RTA_PAYLOAD ( tb [ TCA_TCINDEX_MASK - 1 ] ) < sizeof ( u16 ) )
goto errout ;
cp . mask = * ( u16 * ) RTA_DATA ( tb [ TCA_TCINDEX_MASK - 1 ] ) ;
}
if ( tb [ TCA_TCINDEX_SHIFT - 1 ] ) {
2007-04-09 22:42:25 +04:00
if ( RTA_PAYLOAD ( tb [ TCA_TCINDEX_SHIFT - 1 ] ) < sizeof ( int ) )
2005-04-17 02:20:36 +04:00
goto errout ;
2007-04-09 22:42:25 +04:00
cp . shift = * ( int * ) RTA_DATA ( tb [ TCA_TCINDEX_SHIFT - 1 ] ) ;
2005-04-17 02:20:36 +04:00
}
err = - EBUSY ;
/* Hash already allocated, make sure that we still meet the
* requirements for the allocated hash .
*/
if ( cp . perfect ) {
if ( ! valid_perfect_hash ( & cp ) | |
cp . hash > cp . alloc_hash )
goto errout ;
} else if ( cp . h & & cp . hash ! = cp . alloc_hash )
goto errout ;
err = - EINVAL ;
if ( tb [ TCA_TCINDEX_FALL_THROUGH - 1 ] ) {
if ( RTA_PAYLOAD ( tb [ TCA_TCINDEX_FALL_THROUGH - 1 ] ) < sizeof ( u32 ) )
goto errout ;
cp . fall_through =
* ( u32 * ) RTA_DATA ( tb [ TCA_TCINDEX_FALL_THROUGH - 1 ] ) ;
}
if ( ! cp . hash ) {
/* Hash not specified, use perfect hash if the upper limit
* of the hashing index is below the threshold .
*/
if ( ( cp . mask > > cp . shift ) < PERFECT_HASH_THRESHOLD )
cp . hash = ( cp . mask > > cp . shift ) + 1 ;
else
cp . hash = DEFAULT_HASH_SIZE ;
}
if ( ! cp . perfect & & ! cp . h )
cp . alloc_hash = cp . hash ;
/* Note: this could be as restrictive as if (handle & ~(mask >> shift))
* but then , we ' d fail handles that may become valid after some future
* mask change . While this is extremely unlikely to ever matter ,
* the check below is safer ( and also more backwards - compatible ) .
*/
if ( cp . perfect | | valid_perfect_hash ( & cp ) )
if ( handle > = cp . alloc_hash )
goto errout ;
err = - ENOMEM ;
if ( ! cp . perfect & & ! cp . h ) {
if ( valid_perfect_hash ( & cp ) ) {
2006-07-22 01:51:30 +04:00
cp . perfect = kcalloc ( cp . hash , sizeof ( * r ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! cp . perfect )
goto errout ;
balloc = 1 ;
} else {
2006-07-22 01:51:30 +04:00
cp . h = kcalloc ( cp . hash , sizeof ( f ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! cp . h )
goto errout ;
balloc = 2 ;
}
}
if ( cp . perfect )
r = cp . perfect + handle ;
else
r = tcindex_lookup ( & cp , handle ) ? : & new_filter_result ;
if ( r = = & new_filter_result ) {
2006-07-22 01:51:30 +04:00
f = kzalloc ( sizeof ( * f ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( ! f )
goto errout_alloc ;
2007-02-09 17:25:16 +03:00
}
2005-04-17 02:20:36 +04:00
if ( tb [ TCA_TCINDEX_CLASSID - 1 ] ) {
cr . res . classid = * ( u32 * ) RTA_DATA ( tb [ TCA_TCINDEX_CLASSID - 1 ] ) ;
tcf_bind_filter ( tp , & cr . res , base ) ;
2007-02-09 17:25:16 +03:00
}
2005-04-17 02:20:36 +04:00
tcf_exts_change ( tp , & cr . exts , & e ) ;
tcf_tree_lock ( tp ) ;
if ( old_r & & old_r ! = r )
memset ( old_r , 0 , sizeof ( * old_r ) ) ;
memcpy ( p , & cp , sizeof ( cp ) ) ;
memcpy ( r , & cr , sizeof ( cr ) ) ;
if ( r = = & new_filter_result ) {
struct tcindex_filter * * fp ;
f - > key = handle ;
f - > result = new_filter_result ;
f - > next = NULL ;
for ( fp = p - > h + ( handle % p - > hash ) ; * fp ; fp = & ( * fp ) - > next )
/* nothing */ ;
* fp = f ;
2007-02-09 17:25:16 +03:00
}
2005-04-17 02:20:36 +04:00
tcf_tree_unlock ( tp ) ;
return 0 ;
errout_alloc :
if ( balloc = = 1 )
kfree ( cp . perfect ) ;
else if ( balloc = = 2 )
kfree ( cp . h ) ;
errout :
tcf_exts_destroy ( tp , & e ) ;
return err ;
}
static int
tcindex_change ( struct tcf_proto * tp , unsigned long base , u32 handle ,
struct rtattr * * tca , unsigned long * arg )
{
struct rtattr * opt = tca [ TCA_OPTIONS - 1 ] ;
struct rtattr * tb [ TCA_TCINDEX_MAX ] ;
struct tcindex_data * p = PRIV ( tp ) ;
struct tcindex_filter_result * r = ( struct tcindex_filter_result * ) * arg ;
2008-01-21 13:26:41 +03:00
pr_debug ( " tcindex_change(tp %p,handle 0x%08x,tca %p,arg %p),opt %p, "
2005-04-17 02:20:36 +04:00
" p %p,r %p,*arg 0x%lx \n " ,
tp , handle , tca , arg , opt , p , r , arg ? * arg : 0L ) ;
if ( ! opt )
return 0 ;
if ( rtattr_parse_nested ( tb , TCA_TCINDEX_MAX , opt ) < 0 )
return - EINVAL ;
return tcindex_set_parms ( tp , base , handle , p , r , tb , tca [ TCA_RATE - 1 ] ) ;
}
static void tcindex_walk ( struct tcf_proto * tp , struct tcf_walker * walker )
{
struct tcindex_data * p = PRIV ( tp ) ;
2008-01-21 13:26:41 +03:00
struct tcindex_filter * f , * next ;
2005-04-17 02:20:36 +04:00
int i ;
2008-01-21 13:26:41 +03:00
pr_debug ( " tcindex_walk(tp %p,walker %p),p %p \n " , tp , walker , p ) ;
2005-04-17 02:20:36 +04:00
if ( p - > perfect ) {
for ( i = 0 ; i < p - > hash ; i + + ) {
if ( ! p - > perfect [ i ] . res . class )
continue ;
if ( walker - > count > = walker - > skip ) {
if ( walker - > fn ( tp ,
( unsigned long ) ( p - > perfect + i ) , walker )
< 0 ) {
walker - > stop = 1 ;
return ;
}
}
walker - > count + + ;
}
}
if ( ! p - > h )
return ;
for ( i = 0 ; i < p - > hash ; i + + ) {
for ( f = p - > h [ i ] ; f ; f = next ) {
next = f - > next ;
if ( walker - > count > = walker - > skip ) {
2008-01-21 13:26:41 +03:00
if ( walker - > fn ( tp , ( unsigned long ) & f - > result ,
2005-04-17 02:20:36 +04:00
walker ) < 0 ) {
walker - > stop = 1 ;
return ;
}
}
walker - > count + + ;
}
}
}
static int tcindex_destroy_element ( struct tcf_proto * tp ,
unsigned long arg , struct tcf_walker * walker )
{
return __tcindex_delete ( tp , arg , 0 ) ;
}
static void tcindex_destroy ( struct tcf_proto * tp )
{
struct tcindex_data * p = PRIV ( tp ) ;
struct tcf_walker walker ;
2008-01-21 13:26:41 +03:00
pr_debug ( " tcindex_destroy(tp %p),p %p \n " , tp , p ) ;
2005-04-17 02:20:36 +04:00
walker . count = 0 ;
walker . skip = 0 ;
walker . fn = & tcindex_destroy_element ;
2008-01-21 13:26:41 +03:00
tcindex_walk ( tp , & walker ) ;
2005-11-08 20:41:34 +03:00
kfree ( p - > perfect ) ;
kfree ( p - > h ) ;
2005-04-17 02:20:36 +04:00
kfree ( p ) ;
tp - > root = NULL ;
}
static int tcindex_dump ( struct tcf_proto * tp , unsigned long fh ,
struct sk_buff * skb , struct tcmsg * t )
{
struct tcindex_data * p = PRIV ( tp ) ;
struct tcindex_filter_result * r = ( struct tcindex_filter_result * ) fh ;
2007-04-20 07:29:13 +04:00
unsigned char * b = skb_tail_pointer ( skb ) ;
2005-04-17 02:20:36 +04:00
struct rtattr * rta ;
2008-01-21 13:26:41 +03:00
pr_debug ( " tcindex_dump(tp %p,fh 0x%lx,skb %p,t %p),p %p,r %p,b %p \n " ,
tp , fh , skb , t , p , r , b ) ;
pr_debug ( " p->perfect %p p->h %p \n " , p - > perfect , p - > h ) ;
2005-04-17 02:20:36 +04:00
rta = ( struct rtattr * ) b ;
2008-01-21 13:26:41 +03:00
RTA_PUT ( skb , TCA_OPTIONS , 0 , NULL ) ;
2005-04-17 02:20:36 +04:00
if ( ! fh ) {
t - > tcm_handle = ~ 0 ; /* whatever ... */
2008-01-21 13:26:41 +03:00
RTA_PUT ( skb , TCA_TCINDEX_HASH , sizeof ( p - > hash ) , & p - > hash ) ;
RTA_PUT ( skb , TCA_TCINDEX_MASK , sizeof ( p - > mask ) , & p - > mask ) ;
RTA_PUT ( skb , TCA_TCINDEX_SHIFT , sizeof ( p - > shift ) , & p - > shift ) ;
RTA_PUT ( skb , TCA_TCINDEX_FALL_THROUGH , sizeof ( p - > fall_through ) ,
2005-04-17 02:20:36 +04:00
& p - > fall_through ) ;
2007-04-20 07:29:13 +04:00
rta - > rta_len = skb_tail_pointer ( skb ) - b ;
2005-04-17 02:20:36 +04:00
} else {
if ( p - > perfect ) {
t - > tcm_handle = r - p - > perfect ;
} else {
struct tcindex_filter * f ;
int i ;
t - > tcm_handle = 0 ;
for ( i = 0 ; ! t - > tcm_handle & & i < p - > hash ; i + + ) {
for ( f = p - > h [ i ] ; ! t - > tcm_handle & & f ;
f = f - > next ) {
if ( & f - > result = = r )
t - > tcm_handle = f - > key ;
}
}
}
2008-01-21 13:26:41 +03:00
pr_debug ( " handle = %d \n " , t - > tcm_handle ) ;
2005-04-17 02:20:36 +04:00
if ( r - > res . class )
RTA_PUT ( skb , TCA_TCINDEX_CLASSID , 4 , & r - > res . classid ) ;
if ( tcf_exts_dump ( skb , & r - > exts , & tcindex_ext_map ) < 0 )
goto rtattr_failure ;
2007-04-20 07:29:13 +04:00
rta - > rta_len = skb_tail_pointer ( skb ) - b ;
2005-04-17 02:20:36 +04:00
if ( tcf_exts_dump_stats ( skb , & r - > exts , & tcindex_ext_map ) < 0 )
goto rtattr_failure ;
}
2007-02-09 17:25:16 +03:00
2005-04-17 02:20:36 +04:00
return skb - > len ;
rtattr_failure :
2007-03-26 10:06:12 +04:00
nlmsg_trim ( skb , b ) ;
2005-04-17 02:20:36 +04:00
return - 1 ;
}
static struct tcf_proto_ops cls_tcindex_ops = {
. next = NULL ,
. kind = " tcindex " ,
. classify = tcindex_classify ,
. init = tcindex_init ,
. destroy = tcindex_destroy ,
. get = tcindex_get ,
. put = tcindex_put ,
. change = tcindex_change ,
. delete = tcindex_delete ,
. walk = tcindex_walk ,
. dump = tcindex_dump ,
. owner = THIS_MODULE ,
} ;
static int __init init_tcindex ( void )
{
return register_tcf_proto_ops ( & cls_tcindex_ops ) ;
}
2007-02-09 17:25:16 +03:00
static void __exit exit_tcindex ( void )
2005-04-17 02:20:36 +04:00
{
unregister_tcf_proto_ops ( & cls_tcindex_ops ) ;
}
module_init ( init_tcindex )
module_exit ( exit_tcindex )
MODULE_LICENSE ( " GPL " ) ;