2005-04-16 15:20:36 -07:00
/*
* net / sched / pedit . c Generic packet editor
*
* 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 .
*
* Authors : Jamal Hadi Salim ( 2002 - 4 )
*/
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/string.h>
# include <linux/errno.h>
# include <linux/skbuff.h>
# include <linux/rtnetlink.h>
# include <linux/module.h>
# include <linux/init.h>
2007-03-25 23:06:12 -07:00
# include <net/netlink.h>
2005-04-16 15:20:36 -07:00
# include <net/pkt_sched.h>
# include <linux/tc_act/tc_pedit.h>
# include <net/tc_act/tc_pedit.h>
2006-08-21 23:54:55 -07:00
# define PEDIT_TAB_MASK 15
static struct tcf_common * tcf_pedit_ht [ PEDIT_TAB_MASK + 1 ] ;
static u32 pedit_idx_gen ;
2005-04-16 15:20:36 -07:00
static DEFINE_RWLOCK ( pedit_lock ) ;
2006-08-21 23:54:55 -07:00
static struct tcf_hashinfo pedit_hash_info = {
. htab = tcf_pedit_ht ,
. hmask = PEDIT_TAB_MASK ,
. lock = & pedit_lock ,
} ;
2005-04-16 15:20:36 -07:00
2008-01-23 20:36:30 -08:00
static const struct nla_policy pedit_policy [ TCA_PEDIT_MAX + 1 ] = {
[ TCA_PEDIT_PARMS ] = { . len = sizeof ( struct tcf_pedit ) } ,
} ;
2008-01-22 22:11:50 -08:00
static int tcf_pedit_init ( struct nlattr * nla , struct nlattr * est ,
2006-08-21 23:54:55 -07:00
struct tc_action * a , int ovr , int bind )
2005-04-16 15:20:36 -07:00
{
2008-01-22 22:11:50 -08:00
struct nlattr * tb [ TCA_PEDIT_MAX + 1 ] ;
2005-04-16 15:20:36 -07:00
struct tc_pedit * parm ;
2008-01-23 20:33:32 -08:00
int ret = 0 , err ;
2005-04-16 15:20:36 -07:00
struct tcf_pedit * p ;
2006-08-21 23:54:55 -07:00
struct tcf_common * pc ;
2005-04-16 15:20:36 -07:00
struct tc_pedit_key * keys = NULL ;
int ksize ;
2008-01-23 20:33:32 -08:00
if ( nla = = NULL )
2005-04-16 15:20:36 -07:00
return - EINVAL ;
2008-01-23 20:36:30 -08:00
err = nla_parse_nested ( tb , TCA_PEDIT_MAX , nla , pedit_policy ) ;
2008-01-23 20:33:32 -08:00
if ( err < 0 )
return err ;
2008-01-23 20:36:30 -08:00
if ( tb [ TCA_PEDIT_PARMS ] = = NULL )
2005-04-16 15:20:36 -07:00
return - EINVAL ;
2008-01-22 22:11:50 -08:00
parm = nla_data ( tb [ TCA_PEDIT_PARMS ] ) ;
2005-04-16 15:20:36 -07:00
ksize = parm - > nkeys * sizeof ( struct tc_pedit_key ) ;
2008-01-22 22:11:50 -08:00
if ( nla_len ( tb [ TCA_PEDIT_PARMS ] ) < sizeof ( * parm ) + ksize )
2005-04-16 15:20:36 -07:00
return - EINVAL ;
2006-08-21 23:54:55 -07:00
pc = tcf_hash_check ( parm - > index , a , bind , & pedit_hash_info ) ;
if ( ! pc ) {
2005-04-16 15:20:36 -07:00
if ( ! parm - > nkeys )
return - EINVAL ;
2006-08-21 23:54:55 -07:00
pc = tcf_hash_create ( parm - > index , est , a , sizeof ( * p ) , bind ,
& pedit_idx_gen , & pedit_hash_info ) ;
if ( unlikely ( ! pc ) )
2005-04-16 15:20:36 -07:00
return - ENOMEM ;
2006-08-21 23:54:55 -07:00
p = to_pedit ( pc ) ;
2005-04-16 15:20:36 -07:00
keys = kmalloc ( ksize , GFP_KERNEL ) ;
if ( keys = = NULL ) {
2006-08-21 23:54:55 -07:00
kfree ( pc ) ;
2005-04-16 15:20:36 -07:00
return - ENOMEM ;
}
ret = ACT_P_CREATED ;
} else {
2006-08-21 23:54:55 -07:00
p = to_pedit ( pc ) ;
2005-04-16 15:20:36 -07:00
if ( ! ovr ) {
2006-08-21 23:54:55 -07:00
tcf_hash_release ( pc , bind , & pedit_hash_info ) ;
2005-04-16 15:20:36 -07:00
return - EEXIST ;
}
2006-08-21 23:54:55 -07:00
if ( p - > tcfp_nkeys & & p - > tcfp_nkeys ! = parm - > nkeys ) {
2005-04-16 15:20:36 -07:00
keys = kmalloc ( ksize , GFP_KERNEL ) ;
if ( keys = = NULL )
return - ENOMEM ;
}
}
2006-08-21 23:54:55 -07:00
spin_lock_bh ( & p - > tcf_lock ) ;
p - > tcfp_flags = parm - > flags ;
p - > tcf_action = parm - > action ;
2005-04-16 15:20:36 -07:00
if ( keys ) {
2006-08-21 23:54:55 -07:00
kfree ( p - > tcfp_keys ) ;
p - > tcfp_keys = keys ;
p - > tcfp_nkeys = parm - > nkeys ;
2005-04-16 15:20:36 -07:00
}
2006-08-21 23:54:55 -07:00
memcpy ( p - > tcfp_keys , parm - > keys , ksize ) ;
spin_unlock_bh ( & p - > tcf_lock ) ;
2005-04-16 15:20:36 -07:00
if ( ret = = ACT_P_CREATED )
2006-08-21 23:54:55 -07:00
tcf_hash_insert ( pc , & pedit_hash_info ) ;
2005-04-16 15:20:36 -07:00
return ret ;
}
2006-08-21 23:54:55 -07:00
static int tcf_pedit_cleanup ( struct tc_action * a , int bind )
2005-04-16 15:20:36 -07:00
{
2006-08-21 23:54:55 -07:00
struct tcf_pedit * p = a - > priv ;
2005-04-16 15:20:36 -07:00
2006-08-21 23:54:55 -07:00
if ( p ) {
struct tc_pedit_key * keys = p - > tcfp_keys ;
if ( tcf_hash_release ( & p - > common , bind , & pedit_hash_info ) ) {
2005-04-16 15:20:36 -07:00
kfree ( keys ) ;
return 1 ;
}
}
return 0 ;
}
2006-08-21 23:54:55 -07:00
static int tcf_pedit ( struct sk_buff * skb , struct tc_action * a ,
struct tcf_result * res )
2005-04-16 15:20:36 -07:00
{
2006-08-21 23:54:55 -07:00
struct tcf_pedit * p = a - > priv ;
2005-04-16 15:20:36 -07:00
int i , munged = 0 ;
u8 * pptr ;
if ( ! ( skb - > tc_verd & TC_OK2MUNGE ) ) {
/* should we set skb->cloned? */
if ( pskb_expand_head ( skb , 0 , 0 , GFP_ATOMIC ) ) {
2006-08-21 23:54:55 -07:00
return p - > tcf_action ;
2005-04-16 15:20:36 -07:00
}
}
2007-04-10 20:50:43 -07:00
pptr = skb_network_header ( skb ) ;
2005-04-16 15:20:36 -07:00
2006-08-21 23:54:55 -07:00
spin_lock ( & p - > tcf_lock ) ;
2005-04-16 15:20:36 -07:00
2006-08-21 23:54:55 -07:00
p - > tcf_tm . lastuse = jiffies ;
2005-04-16 15:20:36 -07:00
2006-08-21 23:54:55 -07:00
if ( p - > tcfp_nkeys > 0 ) {
struct tc_pedit_key * tkey = p - > tcfp_keys ;
2005-04-16 15:20:36 -07:00
2006-08-21 23:54:55 -07:00
for ( i = p - > tcfp_nkeys ; i > 0 ; i - - , tkey + + ) {
2005-04-16 15:20:36 -07:00
u32 * ptr ;
int offset = tkey - > off ;
if ( tkey - > offmask ) {
if ( skb - > len > tkey - > at ) {
char * j = pptr + tkey - > at ;
2007-02-09 23:25:16 +09:00
offset + = ( ( * j & tkey - > offmask ) > >
tkey - > shift ) ;
2005-04-16 15:20:36 -07:00
} else {
goto bad ;
}
}
if ( offset % 4 ) {
printk ( " offset must be on 32 bit boundaries \n " ) ;
goto bad ;
}
2007-05-31 21:33:35 -07:00
if ( offset > 0 & & offset > skb - > len ) {
2005-04-16 15:20:36 -07:00
printk ( " offset %d cant exceed pkt length %d \n " ,
offset , skb - > len ) ;
goto bad ;
}
ptr = ( u32 * ) ( pptr + offset ) ;
/* just do it, baby */
* ptr = ( ( * ptr & tkey - > mask ) ^ tkey - > val ) ;
munged + + ;
}
2007-02-09 23:25:16 +09:00
2005-04-16 15:20:36 -07:00
if ( munged )
skb - > tc_verd = SET_TC_MUNGED ( skb - > tc_verd ) ;
goto done ;
} else {
2006-08-21 23:54:55 -07:00
printk ( " pedit BUG: index %d \n " , p - > tcf_index ) ;
2005-04-16 15:20:36 -07:00
}
bad :
2006-08-21 23:54:55 -07:00
p - > tcf_qstats . overlimits + + ;
2005-04-16 15:20:36 -07:00
done :
2006-08-21 23:54:55 -07:00
p - > tcf_bstats . bytes + = skb - > len ;
p - > tcf_bstats . packets + + ;
spin_unlock ( & p - > tcf_lock ) ;
return p - > tcf_action ;
2005-04-16 15:20:36 -07:00
}
2006-08-21 23:54:55 -07:00
static int tcf_pedit_dump ( struct sk_buff * skb , struct tc_action * a ,
int bind , int ref )
2005-04-16 15:20:36 -07:00
{
2007-04-19 20:29:13 -07:00
unsigned char * b = skb_tail_pointer ( skb ) ;
2006-08-21 23:54:55 -07:00
struct tcf_pedit * p = a - > priv ;
2005-04-16 15:20:36 -07:00
struct tc_pedit * opt ;
struct tcf_t t ;
2007-02-09 23:25:16 +09:00
int s ;
2006-08-21 23:54:55 -07:00
s = sizeof ( * opt ) + p - > tcfp_nkeys * sizeof ( struct tc_pedit_key ) ;
2005-04-16 15:20:36 -07:00
/* netlink spinlocks held above us - must use ATOMIC */
2006-07-21 14:51:30 -07:00
opt = kzalloc ( s , GFP_ATOMIC ) ;
2006-08-21 23:54:55 -07:00
if ( unlikely ( ! opt ) )
2005-04-16 15:20:36 -07:00
return - ENOBUFS ;
2006-08-21 23:54:55 -07:00
memcpy ( opt - > keys , p - > tcfp_keys ,
p - > tcfp_nkeys * sizeof ( struct tc_pedit_key ) ) ;
opt - > index = p - > tcf_index ;
opt - > nkeys = p - > tcfp_nkeys ;
opt - > flags = p - > tcfp_flags ;
opt - > action = p - > tcf_action ;
opt - > refcnt = p - > tcf_refcnt - ref ;
opt - > bindcnt = p - > tcf_bindcnt - bind ;
2005-04-16 15:20:36 -07:00
2008-01-22 22:11:50 -08:00
NLA_PUT ( skb , TCA_PEDIT_PARMS , s , opt ) ;
2006-08-21 23:54:55 -07:00
t . install = jiffies_to_clock_t ( jiffies - p - > tcf_tm . install ) ;
t . lastuse = jiffies_to_clock_t ( jiffies - p - > tcf_tm . lastuse ) ;
t . expires = jiffies_to_clock_t ( p - > tcf_tm . expires ) ;
2008-01-22 22:11:50 -08:00
NLA_PUT ( skb , TCA_PEDIT_TM , sizeof ( t ) , & t ) ;
2006-01-08 22:17:27 -08:00
kfree ( opt ) ;
2005-04-16 15:20:36 -07:00
return skb - > len ;
2008-01-22 22:11:50 -08:00
nla_put_failure :
2007-03-25 23:06:12 -07:00
nlmsg_trim ( skb , b ) ;
2006-01-08 22:17:27 -08:00
kfree ( opt ) ;
2005-04-16 15:20:36 -07:00
return - 1 ;
}
2006-08-21 23:54:55 -07:00
static struct tc_action_ops act_pedit_ops = {
2005-04-16 15:20:36 -07:00
. kind = " pedit " ,
2006-08-21 23:54:55 -07:00
. hinfo = & pedit_hash_info ,
2005-04-16 15:20:36 -07:00
. type = TCA_ACT_PEDIT ,
. capab = TCA_CAP_NONE ,
. owner = THIS_MODULE ,
. act = tcf_pedit ,
. dump = tcf_pedit_dump ,
. cleanup = tcf_pedit_cleanup ,
. lookup = tcf_hash_search ,
. init = tcf_pedit_init ,
. walk = tcf_generic_walker
} ;
MODULE_AUTHOR ( " Jamal Hadi Salim(2002-4) " ) ;
MODULE_DESCRIPTION ( " Generic Packet Editor actions " ) ;
MODULE_LICENSE ( " GPL " ) ;
2006-08-21 23:54:55 -07:00
static int __init pedit_init_module ( void )
2005-04-16 15:20:36 -07:00
{
return tcf_register_action ( & act_pedit_ops ) ;
}
2006-08-21 23:54:55 -07:00
static void __exit pedit_cleanup_module ( void )
2005-04-16 15:20:36 -07:00
{
tcf_unregister_action ( & act_pedit_ops ) ;
}
module_init ( pedit_init_module ) ;
module_exit ( pedit_cleanup_module ) ;