2005-04-17 02:20:36 +04:00
/*
* net / sched / mirred . c packet mirroring and redirect actions
*
* 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 )
*
* TODO : Add ingress support ( and socket redirect support )
*
*/
# 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-09-17 22:56:21 +04:00
# include <net/net_namespace.h>
2007-03-26 10:06:12 +04:00
# include <net/netlink.h>
2005-04-17 02:20:36 +04:00
# include <net/pkt_sched.h>
# include <linux/tc_act/tc_mirred.h>
# include <net/tc_act/tc_mirred.h>
# include <linux/if_arp.h>
2006-08-22 10:54:55 +04:00
# define MIRRED_TAB_MASK 7
static struct tcf_common * tcf_mirred_ht [ MIRRED_TAB_MASK + 1 ] ;
static u32 mirred_idx_gen ;
2005-04-17 02:20:36 +04:00
static DEFINE_RWLOCK ( mirred_lock ) ;
2006-08-22 10:54:55 +04:00
static struct tcf_hashinfo mirred_hash_info = {
. htab = tcf_mirred_ht ,
. hmask = MIRRED_TAB_MASK ,
. lock = & mirred_lock ,
} ;
2005-04-17 02:20:36 +04:00
2006-08-22 10:54:55 +04:00
static inline int tcf_mirred_release ( struct tcf_mirred * m , int bind )
2005-04-17 02:20:36 +04:00
{
2006-08-22 10:54:55 +04:00
if ( m ) {
2005-04-17 02:20:36 +04:00
if ( bind )
2006-08-22 10:54:55 +04:00
m - > tcf_bindcnt - - ;
m - > tcf_refcnt - - ;
if ( ! m - > tcf_bindcnt & & m - > tcf_refcnt < = 0 ) {
dev_put ( m - > tcfm_dev ) ;
tcf_hash_destroy ( & m - > common , & mirred_hash_info ) ;
2005-04-17 02:20:36 +04:00
return 1 ;
}
}
return 0 ;
}
2008-01-24 07:36:30 +03:00
static const struct nla_policy mirred_policy [ TCA_MIRRED_MAX + 1 ] = {
[ TCA_MIRRED_PARMS ] = { . len = sizeof ( struct tc_mirred ) } ,
} ;
2008-01-23 09:11:50 +03:00
static int tcf_mirred_init ( struct nlattr * nla , struct nlattr * est ,
2006-08-22 10:54:55 +04:00
struct tc_action * a , int ovr , int bind )
2005-04-17 02:20:36 +04:00
{
2008-01-23 09:11:50 +03:00
struct nlattr * tb [ TCA_MIRRED_MAX + 1 ] ;
2005-04-17 02:20:36 +04:00
struct tc_mirred * parm ;
2006-08-22 10:54:55 +04:00
struct tcf_mirred * m ;
struct tcf_common * pc ;
2005-04-17 02:20:36 +04:00
struct net_device * dev = NULL ;
2008-01-24 07:33:32 +03:00
int ret = 0 , err ;
2005-04-17 02:20:36 +04:00
int ok_push = 0 ;
2008-01-24 07:33:32 +03:00
if ( nla = = NULL )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2008-01-24 07:36:30 +03:00
err = nla_parse_nested ( tb , TCA_MIRRED_MAX , nla , mirred_policy ) ;
2008-01-24 07:33:32 +03:00
if ( err < 0 )
return err ;
2008-01-24 07:36:30 +03:00
if ( tb [ TCA_MIRRED_PARMS ] = = NULL )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2008-01-23 09:11:50 +03:00
parm = nla_data ( tb [ TCA_MIRRED_PARMS ] ) ;
2005-04-17 02:20:36 +04:00
if ( parm - > ifindex ) {
2007-09-17 22:56:21 +04:00
dev = __dev_get_by_index ( & init_net , parm - > ifindex ) ;
2005-04-17 02:20:36 +04:00
if ( dev = = NULL )
return - ENODEV ;
switch ( dev - > type ) {
case ARPHRD_TUNNEL :
case ARPHRD_TUNNEL6 :
case ARPHRD_SIT :
case ARPHRD_IPGRE :
case ARPHRD_VOID :
case ARPHRD_NONE :
ok_push = 0 ;
break ;
default :
ok_push = 1 ;
break ;
}
}
2006-08-22 10:54:55 +04:00
pc = tcf_hash_check ( parm - > index , a , bind , & mirred_hash_info ) ;
if ( ! pc ) {
2005-04-17 02:20:36 +04:00
if ( ! parm - > ifindex )
return - EINVAL ;
2006-08-22 10:54:55 +04:00
pc = tcf_hash_create ( parm - > index , est , a , sizeof ( * m ) , bind ,
& mirred_idx_gen , & mirred_hash_info ) ;
2008-11-26 08:12:32 +03:00
if ( IS_ERR ( pc ) )
return PTR_ERR ( pc ) ;
2005-04-17 02:20:36 +04:00
ret = ACT_P_CREATED ;
} else {
if ( ! ovr ) {
2006-08-22 10:54:55 +04:00
tcf_mirred_release ( to_mirred ( pc ) , bind ) ;
2005-04-17 02:20:36 +04:00
return - EEXIST ;
}
}
2006-08-22 10:54:55 +04:00
m = to_mirred ( pc ) ;
2005-04-17 02:20:36 +04:00
2006-08-22 10:54:55 +04:00
spin_lock_bh ( & m - > tcf_lock ) ;
m - > tcf_action = parm - > action ;
m - > tcfm_eaction = parm - > eaction ;
2005-04-17 02:20:36 +04:00
if ( parm - > ifindex ) {
2006-08-22 10:54:55 +04:00
m - > tcfm_ifindex = parm - > ifindex ;
2005-04-17 02:20:36 +04:00
if ( ret ! = ACT_P_CREATED )
2006-08-22 10:54:55 +04:00
dev_put ( m - > tcfm_dev ) ;
m - > tcfm_dev = dev ;
2005-04-17 02:20:36 +04:00
dev_hold ( dev ) ;
2006-08-22 10:54:55 +04:00
m - > tcfm_ok_push = ok_push ;
2005-04-17 02:20:36 +04:00
}
2006-08-22 10:54:55 +04:00
spin_unlock_bh ( & m - > tcf_lock ) ;
2005-04-17 02:20:36 +04:00
if ( ret = = ACT_P_CREATED )
2006-08-22 10:54:55 +04:00
tcf_hash_insert ( pc , & mirred_hash_info ) ;
2005-04-17 02:20:36 +04:00
return ret ;
}
2006-08-22 10:54:55 +04:00
static int tcf_mirred_cleanup ( struct tc_action * a , int bind )
2005-04-17 02:20:36 +04:00
{
2006-08-22 10:54:55 +04:00
struct tcf_mirred * m = a - > priv ;
2005-04-17 02:20:36 +04:00
2006-08-22 10:54:55 +04:00
if ( m )
return tcf_mirred_release ( m , bind ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2006-08-22 10:54:55 +04:00
static int tcf_mirred ( struct sk_buff * skb , struct tc_action * a ,
struct tcf_result * res )
2005-04-17 02:20:36 +04:00
{
2006-08-22 10:54:55 +04:00
struct tcf_mirred * m = a - > priv ;
2005-04-17 02:20:36 +04:00
struct net_device * dev ;
struct sk_buff * skb2 = NULL ;
u32 at = G_TC_AT ( skb - > tc_verd ) ;
2006-08-22 10:54:55 +04:00
spin_lock ( & m - > tcf_lock ) ;
2005-04-17 02:20:36 +04:00
2006-08-22 10:54:55 +04:00
dev = m - > tcfm_dev ;
m - > tcf_tm . lastuse = jiffies ;
2005-04-17 02:20:36 +04:00
if ( ! ( dev - > flags & IFF_UP ) ) {
if ( net_ratelimit ( ) )
printk ( " mirred to Houston: device %s is gone! \n " ,
dev - > name ) ;
bad_mirred :
if ( skb2 ! = NULL )
kfree_skb ( skb2 ) ;
2006-08-22 10:54:55 +04:00
m - > tcf_qstats . overlimits + + ;
2008-07-20 11:08:27 +04:00
m - > tcf_bstats . bytes + = qdisc_pkt_len ( skb ) ;
2006-08-22 10:54:55 +04:00
m - > tcf_bstats . packets + + ;
spin_unlock ( & m - > tcf_lock ) ;
2005-04-17 02:20:36 +04:00
/* should we be asking for packet to be dropped?
* may make sense for redirect case only
*/
return TC_ACT_SHOT ;
}
2007-10-26 13:47:54 +04:00
skb2 = skb_act_clone ( skb , GFP_ATOMIC ) ;
2005-04-17 02:20:36 +04:00
if ( skb2 = = NULL )
goto bad_mirred ;
2006-08-22 10:54:55 +04:00
if ( m - > tcfm_eaction ! = TCA_EGRESS_MIRROR & &
m - > tcfm_eaction ! = TCA_EGRESS_REDIR ) {
2005-04-17 02:20:36 +04:00
if ( net_ratelimit ( ) )
2006-08-22 10:54:55 +04:00
printk ( " tcf_mirred unknown action %d \n " ,
m - > tcfm_eaction ) ;
2005-04-17 02:20:36 +04:00
goto bad_mirred ;
}
2008-07-20 11:08:27 +04:00
m - > tcf_bstats . bytes + = qdisc_pkt_len ( skb2 ) ;
2006-08-22 10:54:55 +04:00
m - > tcf_bstats . packets + + ;
2005-04-17 02:20:36 +04:00
if ( ! ( at & AT_EGRESS ) )
2006-08-22 10:54:55 +04:00
if ( m - > tcfm_ok_push )
2005-04-17 02:20:36 +04:00
skb_push ( skb2 , skb2 - > dev - > hard_header_len ) ;
/* mirror is always swallowed */
2006-08-22 10:54:55 +04:00
if ( m - > tcfm_eaction ! = TCA_EGRESS_MIRROR )
2005-04-17 02:20:36 +04:00
skb2 - > tc_verd = SET_TC_FROM ( skb2 - > tc_verd , at ) ;
skb2 - > dev = dev ;
2007-03-29 22:46:52 +04:00
skb2 - > iif = skb - > dev - > ifindex ;
2005-04-17 02:20:36 +04:00
dev_queue_xmit ( skb2 ) ;
2006-08-22 10:54:55 +04:00
spin_unlock ( & m - > tcf_lock ) ;
return m - > tcf_action ;
2005-04-17 02:20:36 +04:00
}
2006-08-22 10:54:55 +04:00
static int tcf_mirred_dump ( struct sk_buff * skb , struct tc_action * a , int bind , int ref )
2005-04-17 02:20:36 +04:00
{
2007-04-20 07:29:13 +04:00
unsigned char * b = skb_tail_pointer ( skb ) ;
2006-08-22 10:54:55 +04:00
struct tcf_mirred * m = a - > priv ;
2005-04-17 02:20:36 +04:00
struct tc_mirred opt ;
struct tcf_t t ;
2006-08-22 10:54:55 +04:00
opt . index = m - > tcf_index ;
opt . action = m - > tcf_action ;
opt . refcnt = m - > tcf_refcnt - ref ;
opt . bindcnt = m - > tcf_bindcnt - bind ;
opt . eaction = m - > tcfm_eaction ;
opt . ifindex = m - > tcfm_ifindex ;
2008-01-23 09:11:50 +03:00
NLA_PUT ( skb , TCA_MIRRED_PARMS , sizeof ( opt ) , & opt ) ;
2006-08-22 10:54:55 +04:00
t . install = jiffies_to_clock_t ( jiffies - m - > tcf_tm . install ) ;
t . lastuse = jiffies_to_clock_t ( jiffies - m - > tcf_tm . lastuse ) ;
t . expires = jiffies_to_clock_t ( m - > tcf_tm . expires ) ;
2008-01-23 09:11:50 +03:00
NLA_PUT ( skb , TCA_MIRRED_TM , sizeof ( t ) , & t ) ;
2005-04-17 02:20:36 +04:00
return skb - > len ;
2008-01-23 09:11:50 +03:00
nla_put_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 tc_action_ops act_mirred_ops = {
. kind = " mirred " ,
2006-08-22 10:54:55 +04:00
. hinfo = & mirred_hash_info ,
2005-04-17 02:20:36 +04:00
. type = TCA_ACT_MIRRED ,
. capab = TCA_CAP_NONE ,
. owner = THIS_MODULE ,
. act = tcf_mirred ,
. dump = tcf_mirred_dump ,
. cleanup = tcf_mirred_cleanup ,
. lookup = tcf_hash_search ,
. init = tcf_mirred_init ,
. walk = tcf_generic_walker
} ;
MODULE_AUTHOR ( " Jamal Hadi Salim(2002) " ) ;
MODULE_DESCRIPTION ( " Device Mirror/redirect actions " ) ;
MODULE_LICENSE ( " GPL " ) ;
2006-08-22 10:54:55 +04:00
static int __init mirred_init_module ( void )
2005-04-17 02:20:36 +04:00
{
printk ( " Mirror/redirect action on \n " ) ;
return tcf_register_action ( & act_mirred_ops ) ;
}
2006-08-22 10:54:55 +04:00
static void __exit mirred_cleanup_module ( void )
2005-04-17 02:20:36 +04:00
{
tcf_unregister_action ( & act_mirred_ops ) ;
}
module_init ( mirred_init_module ) ;
module_exit ( mirred_cleanup_module ) ;