2016-09-13 03:13:09 +03:00
/*
* net / sched / act_skbmod . c skb data modifier
*
* Copyright ( c ) 2016 Jamal Hadi Salim < jhs @ mojatatu . com >
*
* 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 .
*/
# include <linux/module.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/skbuff.h>
# include <linux/rtnetlink.h>
# include <net/netlink.h>
# include <net/pkt_sched.h>
# include <linux/tc_act/tc_skbmod.h>
# include <net/tc_act/tc_skbmod.h>
netns: make struct pernet_operations::id unsigned int
Make struct pernet_operations::id unsigned.
There are 2 reasons to do so:
1)
This field is really an index into an zero based array and
thus is unsigned entity. Using negative value is out-of-bound
access by definition.
2)
On x86_64 unsigned 32-bit data which are mixed with pointers
via array indexing or offsets added or subtracted to pointers
are preffered to signed 32-bit data.
"int" being used as an array index needs to be sign-extended
to 64-bit before being used.
void f(long *p, int i)
{
g(p[i]);
}
roughly translates to
movsx rsi, esi
mov rdi, [rsi+...]
call g
MOVSX is 3 byte instruction which isn't necessary if the variable is
unsigned because x86_64 is zero extending by default.
Now, there is net_generic() function which, you guessed it right, uses
"int" as an array index:
static inline void *net_generic(const struct net *net, int id)
{
...
ptr = ng->ptr[id - 1];
...
}
And this function is used a lot, so those sign extensions add up.
Patch snipes ~1730 bytes on allyesconfig kernel (without all junk
messing with code generation):
add/remove: 0/0 grow/shrink: 70/598 up/down: 396/-2126 (-1730)
Unfortunately some functions actually grow bigger.
This is a semmingly random artefact of code generation with register
allocator being used differently. gcc decides that some variable
needs to live in new r8+ registers and every access now requires REX
prefix. Or it is shifted into r12, so [r12+0] addressing mode has to be
used which is longer than [r8]
However, overall balance is in negative direction:
add/remove: 0/0 grow/shrink: 70/598 up/down: 396/-2126 (-1730)
function old new delta
nfsd4_lock 3886 3959 +73
tipc_link_build_proto_msg 1096 1140 +44
mac80211_hwsim_new_radio 2776 2808 +32
tipc_mon_rcv 1032 1058 +26
svcauth_gss_legacy_init 1413 1429 +16
tipc_bcbase_select_primary 379 392 +13
nfsd4_exchange_id 1247 1260 +13
nfsd4_setclientid_confirm 782 793 +11
...
put_client_renew_locked 494 480 -14
ip_set_sockfn_get 730 716 -14
geneve_sock_add 829 813 -16
nfsd4_sequence_done 721 703 -18
nlmclnt_lookup_host 708 686 -22
nfsd4_lockt 1085 1063 -22
nfs_get_client 1077 1050 -27
tcf_bpf_init 1106 1076 -30
nfsd4_encode_fattr 5997 5930 -67
Total: Before=154856051, After=154854321, chg -0.00%
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2016-11-17 04:58:21 +03:00
static unsigned int skbmod_net_id ;
2016-09-13 03:13:09 +03:00
static struct tc_action_ops act_skbmod_ops ;
# define MAX_EDIT_LEN ETH_HLEN
static int tcf_skbmod_run ( struct sk_buff * skb , const struct tc_action * a ,
struct tcf_result * res )
{
struct tcf_skbmod * d = to_skbmod ( a ) ;
int action ;
struct tcf_skbmod_params * p ;
u64 flags ;
int err ;
tcf_lastuse_update ( & d - > tcf_tm ) ;
bstats_cpu_update ( this_cpu_ptr ( d - > common . cpu_bstats ) , skb ) ;
/* XXX: if you are going to edit more fields beyond ethernet header
* ( example when you add IP header replacement or vlan swap )
* then MAX_EDIT_LEN needs to change appropriately
*/
err = skb_ensure_writable ( skb , MAX_EDIT_LEN ) ;
if ( unlikely ( err ) ) { /* best policy is to drop on the floor */
qstats_overlimit_inc ( this_cpu_ptr ( d - > common . cpu_qstats ) ) ;
return TC_ACT_SHOT ;
}
rcu_read_lock ( ) ;
action = READ_ONCE ( d - > tcf_action ) ;
if ( unlikely ( action = = TC_ACT_SHOT ) ) {
qstats_overlimit_inc ( this_cpu_ptr ( d - > common . cpu_qstats ) ) ;
rcu_read_unlock ( ) ;
return action ;
}
p = rcu_dereference ( d - > skbmod_p ) ;
flags = p - > flags ;
if ( flags & SKBMOD_F_DMAC )
ether_addr_copy ( eth_hdr ( skb ) - > h_dest , p - > eth_dst ) ;
if ( flags & SKBMOD_F_SMAC )
ether_addr_copy ( eth_hdr ( skb ) - > h_source , p - > eth_src ) ;
if ( flags & SKBMOD_F_ETYPE )
eth_hdr ( skb ) - > h_proto = p - > eth_type ;
rcu_read_unlock ( ) ;
if ( flags & SKBMOD_F_SWAPMAC ) {
u16 tmpaddr [ ETH_ALEN / 2 ] ; /* ether_addr_copy() requirement */
/*XXX: I am sure we can come up with more efficient swapping*/
ether_addr_copy ( ( u8 * ) tmpaddr , eth_hdr ( skb ) - > h_dest ) ;
ether_addr_copy ( eth_hdr ( skb ) - > h_dest , eth_hdr ( skb ) - > h_source ) ;
ether_addr_copy ( eth_hdr ( skb ) - > h_source , ( u8 * ) tmpaddr ) ;
}
return action ;
}
static const struct nla_policy skbmod_policy [ TCA_SKBMOD_MAX + 1 ] = {
[ TCA_SKBMOD_PARMS ] = { . len = sizeof ( struct tc_skbmod ) } ,
[ TCA_SKBMOD_DMAC ] = { . len = ETH_ALEN } ,
[ TCA_SKBMOD_SMAC ] = { . len = ETH_ALEN } ,
[ TCA_SKBMOD_ETYPE ] = { . type = NLA_U16 } ,
} ;
static int tcf_skbmod_init ( struct net * net , struct nlattr * nla ,
struct nlattr * est , struct tc_action * * a ,
int ovr , int bind )
{
struct tc_action_net * tn = net_generic ( net , skbmod_net_id ) ;
struct nlattr * tb [ TCA_SKBMOD_MAX + 1 ] ;
struct tcf_skbmod_params * p , * p_old ;
struct tc_skbmod * parm ;
struct tcf_skbmod * d ;
bool exists = false ;
u8 * daddr = NULL ;
u8 * saddr = NULL ;
u16 eth_type = 0 ;
u32 lflags = 0 ;
int ret = 0 , err ;
if ( ! nla )
return - EINVAL ;
2017-04-12 15:34:07 +03:00
err = nla_parse_nested ( tb , TCA_SKBMOD_MAX , nla , skbmod_policy , NULL ) ;
2016-09-13 03:13:09 +03:00
if ( err < 0 )
return err ;
if ( ! tb [ TCA_SKBMOD_PARMS ] )
return - EINVAL ;
if ( tb [ TCA_SKBMOD_DMAC ] ) {
daddr = nla_data ( tb [ TCA_SKBMOD_DMAC ] ) ;
lflags | = SKBMOD_F_DMAC ;
}
if ( tb [ TCA_SKBMOD_SMAC ] ) {
saddr = nla_data ( tb [ TCA_SKBMOD_SMAC ] ) ;
lflags | = SKBMOD_F_SMAC ;
}
if ( tb [ TCA_SKBMOD_ETYPE ] ) {
eth_type = nla_get_u16 ( tb [ TCA_SKBMOD_ETYPE ] ) ;
lflags | = SKBMOD_F_ETYPE ;
}
parm = nla_data ( tb [ TCA_SKBMOD_PARMS ] ) ;
if ( parm - > flags & SKBMOD_F_SWAPMAC )
lflags = SKBMOD_F_SWAPMAC ;
2017-08-30 09:31:59 +03:00
exists = tcf_idr_check ( tn , parm - > index , a , bind ) ;
2016-09-13 03:13:09 +03:00
if ( exists & & bind )
return 0 ;
if ( ! lflags )
return - EINVAL ;
if ( ! exists ) {
2017-08-30 09:31:59 +03:00
ret = tcf_idr_create ( tn , parm - > index , est , a ,
& act_skbmod_ops , bind , true ) ;
2016-09-13 03:13:09 +03:00
if ( ret )
return ret ;
ret = ACT_P_CREATED ;
} else {
2017-08-30 09:31:59 +03:00
tcf_idr_release ( * a , bind ) ;
2016-09-13 03:13:09 +03:00
if ( ! ovr )
return - EEXIST ;
}
d = to_skbmod ( * a ) ;
ASSERT_RTNL ( ) ;
p = kzalloc ( sizeof ( struct tcf_skbmod_params ) , GFP_KERNEL ) ;
if ( unlikely ( ! p ) ) {
if ( ovr )
2017-08-30 09:31:59 +03:00
tcf_idr_release ( * a , bind ) ;
2016-09-13 03:13:09 +03:00
return - ENOMEM ;
}
p - > flags = lflags ;
d - > tcf_action = parm - > action ;
p_old = rtnl_dereference ( d - > skbmod_p ) ;
if ( ovr )
spin_lock_bh ( & d - > tcf_lock ) ;
if ( lflags & SKBMOD_F_DMAC )
ether_addr_copy ( p - > eth_dst , daddr ) ;
if ( lflags & SKBMOD_F_SMAC )
ether_addr_copy ( p - > eth_src , saddr ) ;
if ( lflags & SKBMOD_F_ETYPE )
p - > eth_type = htons ( eth_type ) ;
rcu_assign_pointer ( d - > skbmod_p , p ) ;
if ( ovr )
spin_unlock_bh ( & d - > tcf_lock ) ;
if ( p_old )
kfree_rcu ( p_old , rcu ) ;
if ( ret = = ACT_P_CREATED )
2017-08-30 09:31:59 +03:00
tcf_idr_insert ( tn , * a ) ;
2016-09-13 03:13:09 +03:00
return ret ;
}
static void tcf_skbmod_cleanup ( struct tc_action * a , int bind )
{
struct tcf_skbmod * d = to_skbmod ( a ) ;
struct tcf_skbmod_params * p ;
p = rcu_dereference_protected ( d - > skbmod_p , 1 ) ;
kfree_rcu ( p , rcu ) ;
}
static int tcf_skbmod_dump ( struct sk_buff * skb , struct tc_action * a ,
int bind , int ref )
{
struct tcf_skbmod * d = to_skbmod ( a ) ;
unsigned char * b = skb_tail_pointer ( skb ) ;
struct tcf_skbmod_params * p = rtnl_dereference ( d - > skbmod_p ) ;
struct tc_skbmod opt = {
. index = d - > tcf_index ,
. refcnt = d - > tcf_refcnt - ref ,
. bindcnt = d - > tcf_bindcnt - bind ,
. action = d - > tcf_action ,
} ;
struct tcf_t t ;
opt . flags = p - > flags ;
if ( nla_put ( skb , TCA_SKBMOD_PARMS , sizeof ( opt ) , & opt ) )
goto nla_put_failure ;
if ( ( p - > flags & SKBMOD_F_DMAC ) & &
nla_put ( skb , TCA_SKBMOD_DMAC , ETH_ALEN , p - > eth_dst ) )
goto nla_put_failure ;
if ( ( p - > flags & SKBMOD_F_SMAC ) & &
nla_put ( skb , TCA_SKBMOD_SMAC , ETH_ALEN , p - > eth_src ) )
goto nla_put_failure ;
if ( ( p - > flags & SKBMOD_F_ETYPE ) & &
nla_put_u16 ( skb , TCA_SKBMOD_ETYPE , ntohs ( p - > eth_type ) ) )
goto nla_put_failure ;
tcf_tm_dump ( & t , & d - > tcf_tm ) ;
if ( nla_put_64bit ( skb , TCA_SKBMOD_TM , sizeof ( t ) , & t , TCA_SKBMOD_PAD ) )
goto nla_put_failure ;
return skb - > len ;
nla_put_failure :
nlmsg_trim ( skb , b ) ;
return - 1 ;
}
static int tcf_skbmod_walker ( struct net * net , struct sk_buff * skb ,
struct netlink_callback * cb , int type ,
const struct tc_action_ops * ops )
{
struct tc_action_net * tn = net_generic ( net , skbmod_net_id ) ;
return tcf_generic_walker ( tn , skb , cb , type , ops ) ;
}
static int tcf_skbmod_search ( struct net * net , struct tc_action * * a , u32 index )
{
struct tc_action_net * tn = net_generic ( net , skbmod_net_id ) ;
2017-08-30 09:31:59 +03:00
return tcf_idr_search ( tn , a , index ) ;
2016-09-13 03:13:09 +03:00
}
static struct tc_action_ops act_skbmod_ops = {
. kind = " skbmod " ,
. type = TCA_ACT_SKBMOD ,
. owner = THIS_MODULE ,
. act = tcf_skbmod_run ,
. dump = tcf_skbmod_dump ,
. init = tcf_skbmod_init ,
. cleanup = tcf_skbmod_cleanup ,
. walk = tcf_skbmod_walker ,
. lookup = tcf_skbmod_search ,
. size = sizeof ( struct tcf_skbmod ) ,
} ;
static __net_init int skbmod_init_net ( struct net * net )
{
struct tc_action_net * tn = net_generic ( net , skbmod_net_id ) ;
2017-11-07 00:47:18 +03:00
return tc_action_net_init ( tn , & act_skbmod_ops ) ;
2016-09-13 03:13:09 +03:00
}
static void __net_exit skbmod_exit_net ( struct net * net )
{
struct tc_action_net * tn = net_generic ( net , skbmod_net_id ) ;
tc_action_net_exit ( tn ) ;
}
static struct pernet_operations skbmod_net_ops = {
. init = skbmod_init_net ,
. exit = skbmod_exit_net ,
. id = & skbmod_net_id ,
. size = sizeof ( struct tc_action_net ) ,
} ;
MODULE_AUTHOR ( " Jamal Hadi Salim, <jhs@mojatatu.com> " ) ;
MODULE_DESCRIPTION ( " SKB data mod-ing " ) ;
MODULE_LICENSE ( " GPL " ) ;
static int __init skbmod_init_module ( void )
{
return tcf_register_action ( & act_skbmod_ops , & skbmod_net_ops ) ;
}
static void __exit skbmod_cleanup_module ( void )
{
tcf_unregister_action ( & act_skbmod_ops , & skbmod_net_ops ) ;
}
module_init ( skbmod_init_module ) ;
module_exit ( skbmod_cleanup_module ) ;