2007-09-27 23:48:05 +04:00
/*
* Stateless NAT actions
*
* Copyright ( c ) 2007 Herbert Xu < herbert @ gondor . apana . org . au >
*
* 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/errno.h>
# include <linux/init.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/netfilter.h>
# include <linux/rtnetlink.h>
# include <linux/skbuff.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/string.h>
# include <linux/tc_act/tc_nat.h>
# include <net/act_api.h>
# include <net/icmp.h>
# include <net/ip.h>
# include <net/netlink.h>
# include <net/tc_act/tc_nat.h>
# include <net/tcp.h>
# include <net/udp.h>
# define NAT_TAB_MASK 15
2016-02-23 02:57:53 +03:00
static int nat_net_id ;
2008-01-24 07:36:30 +03:00
static const struct nla_policy nat_policy [ TCA_NAT_MAX + 1 ] = {
[ TCA_NAT_PARMS ] = { . len = sizeof ( struct tc_nat ) } ,
} ;
2013-01-14 09:15:39 +04:00
static int tcf_nat_init ( struct net * net , struct nlattr * nla , struct nlattr * est ,
2007-09-27 23:48:05 +04:00
struct tc_action * a , int ovr , int bind )
{
2016-02-23 02:57:53 +03:00
struct tc_action_net * tn = net_generic ( net , nat_net_id ) ;
2008-01-23 09:11:50 +03:00
struct nlattr * tb [ TCA_NAT_MAX + 1 ] ;
2007-09-27 23:48:05 +04:00
struct tc_nat * parm ;
2008-01-24 07:33:32 +03:00
int ret = 0 , err ;
2007-09-27 23:48:05 +04:00
struct tcf_nat * p ;
2008-01-24 07:33:32 +03:00
if ( nla = = NULL )
2007-09-27 23:48:05 +04:00
return - EINVAL ;
2008-01-24 07:36:30 +03:00
err = nla_parse_nested ( tb , TCA_NAT_MAX , nla , nat_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_NAT_PARMS ] = = NULL )
2007-09-27 23:48:05 +04:00
return - EINVAL ;
2008-01-23 09:11:50 +03:00
parm = nla_data ( tb [ TCA_NAT_PARMS ] ) ;
2007-09-27 23:48:05 +04:00
2016-02-23 02:57:53 +03:00
if ( ! tcf_hash_check ( tn , parm - > index , a , bind ) ) {
ret = tcf_hash_create ( tn , parm - > index , est , a ,
sizeof ( * p ) , bind , false ) ;
2014-02-12 05:07:31 +04:00
if ( ret )
return ret ;
2007-09-27 23:48:05 +04:00
ret = ACT_P_CREATED ;
} else {
2013-12-23 17:02:11 +04:00
if ( bind )
return 0 ;
2014-02-12 05:07:31 +04:00
tcf_hash_release ( a , bind ) ;
2013-12-23 17:02:11 +04:00
if ( ! ovr )
2007-09-27 23:48:05 +04:00
return - EEXIST ;
}
2014-02-12 05:07:31 +04:00
p = to_tcf_nat ( a ) ;
2007-09-27 23:48:05 +04:00
spin_lock_bh ( & p - > tcf_lock ) ;
p - > old_addr = parm - > old_addr ;
p - > new_addr = parm - > new_addr ;
p - > mask = parm - > mask ;
p - > flags = parm - > flags ;
p - > tcf_action = parm - > action ;
spin_unlock_bh ( & p - > tcf_lock ) ;
if ( ret = = ACT_P_CREATED )
2016-02-23 02:57:53 +03:00
tcf_hash_insert ( tn , a ) ;
2007-09-27 23:48:05 +04:00
return ret ;
}
2011-07-06 03:25:42 +04:00
static int tcf_nat ( struct sk_buff * skb , const struct tc_action * a ,
2007-09-27 23:48:05 +04:00
struct tcf_result * res )
{
struct tcf_nat * p = a - > priv ;
struct iphdr * iph ;
__be32 old_addr ;
__be32 new_addr ;
__be32 mask ;
__be32 addr ;
int egress ;
int action ;
int ihl ;
2010-08-03 21:39:18 +04:00
int noff ;
2007-09-27 23:48:05 +04:00
spin_lock ( & p - > tcf_lock ) ;
2016-06-06 13:32:53 +03:00
tcf_lastuse_update ( & p - > tcf_tm ) ;
2007-09-27 23:48:05 +04:00
old_addr = p - > old_addr ;
new_addr = p - > new_addr ;
mask = p - > mask ;
egress = p - > flags & TCA_NAT_FLAG_EGRESS ;
action = p - > tcf_action ;
2011-01-09 11:30:54 +03:00
bstats_update ( & p - > tcf_bstats , skb ) ;
2007-09-27 23:48:05 +04:00
spin_unlock ( & p - > tcf_lock ) ;
if ( unlikely ( action = = TC_ACT_SHOT ) )
goto drop ;
2010-08-03 21:39:18 +04:00
noff = skb_network_offset ( skb ) ;
if ( ! pskb_may_pull ( skb , sizeof ( * iph ) + noff ) )
2007-09-27 23:48:05 +04:00
goto drop ;
iph = ip_hdr ( skb ) ;
if ( egress )
addr = iph - > saddr ;
else
addr = iph - > daddr ;
if ( ! ( ( old_addr ^ addr ) & mask ) ) {
2016-02-20 01:05:25 +03:00
if ( skb_try_make_writable ( skb , sizeof ( * iph ) + noff ) )
2007-09-27 23:48:05 +04:00
goto drop ;
new_addr & = mask ;
new_addr | = addr & ~ mask ;
/* Rewrite IP header */
iph = ip_hdr ( skb ) ;
if ( egress )
iph - > saddr = new_addr ;
else
iph - > daddr = new_addr ;
2007-11-29 17:17:11 +03:00
csum_replace4 ( & iph - > check , addr , new_addr ) ;
2010-05-29 18:26:59 +04:00
} else if ( ( iph - > frag_off & htons ( IP_OFFSET ) ) | |
iph - > protocol ! = IPPROTO_ICMP ) {
goto out ;
2007-09-27 23:48:05 +04:00
}
ihl = iph - > ihl * 4 ;
/* It would be nice to share code with stateful NAT. */
switch ( iph - > frag_off & htons ( IP_OFFSET ) ? 0 : iph - > protocol ) {
case IPPROTO_TCP :
{
struct tcphdr * tcph ;
2010-08-03 21:39:18 +04:00
if ( ! pskb_may_pull ( skb , ihl + sizeof ( * tcph ) + noff ) | |
2016-02-20 01:05:25 +03:00
skb_try_make_writable ( skb , ihl + sizeof ( * tcph ) + noff ) )
2007-09-27 23:48:05 +04:00
goto drop ;
tcph = ( void * ) ( skb_network_header ( skb ) + ihl ) ;
2015-08-17 23:42:25 +03:00
inet_proto_csum_replace4 ( & tcph - > check , skb , addr , new_addr ,
true ) ;
2007-09-27 23:48:05 +04:00
break ;
}
case IPPROTO_UDP :
{
struct udphdr * udph ;
2010-08-03 21:39:18 +04:00
if ( ! pskb_may_pull ( skb , ihl + sizeof ( * udph ) + noff ) | |
2016-02-20 01:05:25 +03:00
skb_try_make_writable ( skb , ihl + sizeof ( * udph ) + noff ) )
2007-09-27 23:48:05 +04:00
goto drop ;
udph = ( void * ) ( skb_network_header ( skb ) + ihl ) ;
if ( udph - > check | | skb - > ip_summed = = CHECKSUM_PARTIAL ) {
2007-11-29 17:17:11 +03:00
inet_proto_csum_replace4 ( & udph - > check , skb , addr ,
2015-08-17 23:42:25 +03:00
new_addr , true ) ;
2007-09-27 23:48:05 +04:00
if ( ! udph - > check )
udph - > check = CSUM_MANGLED_0 ;
}
break ;
}
case IPPROTO_ICMP :
{
struct icmphdr * icmph ;
2010-08-03 21:39:18 +04:00
if ( ! pskb_may_pull ( skb , ihl + sizeof ( * icmph ) + noff ) )
2007-09-27 23:48:05 +04:00
goto drop ;
icmph = ( void * ) ( skb_network_header ( skb ) + ihl ) ;
if ( ( icmph - > type ! = ICMP_DEST_UNREACH ) & &
( icmph - > type ! = ICMP_TIME_EXCEEDED ) & &
( icmph - > type ! = ICMP_PARAMETERPROB ) )
break ;
2010-08-03 21:39:18 +04:00
if ( ! pskb_may_pull ( skb , ihl + sizeof ( * icmph ) + sizeof ( * iph ) +
noff ) )
2010-07-09 19:33:25 +04:00
goto drop ;
2010-07-29 17:41:46 +04:00
icmph = ( void * ) ( skb_network_header ( skb ) + ihl ) ;
2007-09-27 23:48:05 +04:00
iph = ( void * ) ( icmph + 1 ) ;
if ( egress )
addr = iph - > daddr ;
else
addr = iph - > saddr ;
if ( ( old_addr ^ addr ) & mask )
break ;
2016-02-20 01:05:25 +03:00
if ( skb_try_make_writable ( skb , ihl + sizeof ( * icmph ) +
sizeof ( * iph ) + noff ) )
2007-09-27 23:48:05 +04:00
goto drop ;
icmph = ( void * ) ( skb_network_header ( skb ) + ihl ) ;
iph = ( void * ) ( icmph + 1 ) ;
new_addr & = mask ;
new_addr | = addr & ~ mask ;
/* XXX Fix up the inner checksums. */
if ( egress )
iph - > daddr = new_addr ;
else
iph - > saddr = new_addr ;
2007-11-29 17:17:11 +03:00
inet_proto_csum_replace4 ( & icmph - > checksum , skb , addr , new_addr ,
2015-08-17 23:42:25 +03:00
false ) ;
2007-09-27 23:48:05 +04:00
break ;
}
default :
break ;
}
2010-05-29 18:26:59 +04:00
out :
2007-09-27 23:48:05 +04:00
return action ;
drop :
spin_lock ( & p - > tcf_lock ) ;
p - > tcf_qstats . drops + + ;
spin_unlock ( & p - > tcf_lock ) ;
return TC_ACT_SHOT ;
}
static int tcf_nat_dump ( struct sk_buff * skb , struct tc_action * a ,
int bind , int ref )
{
unsigned char * b = skb_tail_pointer ( skb ) ;
struct tcf_nat * p = a - > priv ;
2010-08-17 00:04:22 +04:00
struct tc_nat opt = {
. old_addr = p - > old_addr ,
. new_addr = p - > new_addr ,
. mask = p - > mask ,
. flags = p - > flags ,
. index = p - > tcf_index ,
. action = p - > tcf_action ,
. refcnt = p - > tcf_refcnt - ref ,
. bindcnt = p - > tcf_bindcnt - bind ,
} ;
2007-09-27 23:48:05 +04:00
struct tcf_t t ;
2012-03-29 13:11:39 +04:00
if ( nla_put ( skb , TCA_NAT_PARMS , sizeof ( opt ) , & opt ) )
goto nla_put_failure ;
2016-06-06 13:32:55 +03:00
tcf_tm_dump ( & t , & p - > tcf_tm ) ;
2016-04-26 11:06:18 +03:00
if ( nla_put_64bit ( skb , TCA_NAT_TM , sizeof ( t ) , & t , TCA_NAT_PAD ) )
2012-03-29 13:11:39 +04:00
goto nla_put_failure ;
2007-09-27 23:48:05 +04:00
return skb - > len ;
2008-01-23 09:11:50 +03:00
nla_put_failure :
2007-09-27 23:48:05 +04:00
nlmsg_trim ( skb , b ) ;
return - 1 ;
}
2016-02-23 02:57:53 +03:00
static int tcf_nat_walker ( struct net * net , struct sk_buff * skb ,
struct netlink_callback * cb , int type ,
struct tc_action * a )
{
struct tc_action_net * tn = net_generic ( net , nat_net_id ) ;
return tcf_generic_walker ( tn , skb , cb , type , a ) ;
}
static int tcf_nat_search ( struct net * net , struct tc_action * a , u32 index )
{
struct tc_action_net * tn = net_generic ( net , nat_net_id ) ;
return tcf_hash_search ( tn , a , index ) ;
}
2007-09-27 23:48:05 +04:00
static struct tc_action_ops act_nat_ops = {
. kind = " nat " ,
. type = TCA_ACT_NAT ,
. owner = THIS_MODULE ,
. act = tcf_nat ,
. dump = tcf_nat_dump ,
. init = tcf_nat_init ,
2016-02-23 02:57:53 +03:00
. walk = tcf_nat_walker ,
. lookup = tcf_nat_search ,
} ;
static __net_init int nat_init_net ( struct net * net )
{
struct tc_action_net * tn = net_generic ( net , nat_net_id ) ;
return tc_action_net_init ( tn , & act_nat_ops , NAT_TAB_MASK ) ;
}
static void __net_exit nat_exit_net ( struct net * net )
{
struct tc_action_net * tn = net_generic ( net , nat_net_id ) ;
tc_action_net_exit ( tn ) ;
}
static struct pernet_operations nat_net_ops = {
. init = nat_init_net ,
. exit = nat_exit_net ,
. id = & nat_net_id ,
. size = sizeof ( struct tc_action_net ) ,
2007-09-27 23:48:05 +04:00
} ;
MODULE_DESCRIPTION ( " Stateless NAT actions " ) ;
MODULE_LICENSE ( " GPL " ) ;
static int __init nat_init_module ( void )
{
2016-02-23 02:57:53 +03:00
return tcf_register_action ( & act_nat_ops , & nat_net_ops ) ;
2007-09-27 23:48:05 +04:00
}
static void __exit nat_cleanup_module ( void )
{
2016-02-23 02:57:53 +03:00
tcf_unregister_action ( & act_nat_ops , & nat_net_ops ) ;
2007-09-27 23:48:05 +04:00
}
module_init ( nat_init_module ) ;
module_exit ( nat_cleanup_module ) ;