2005-04-17 02:20:36 +04:00
/*
* Copyright ( C ) 2003 , 2004 USAGI / WIDE Project
*
* 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 .
2007-02-09 17:24:49 +03:00
*
2005-04-17 02:20:36 +04:00
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
2007-02-09 17:24:49 +03:00
*
2005-04-17 02:20:36 +04:00
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*
* Authors Mitsuru KANDA < mk @ linux - ipv6 . org >
* YOSHIFUJI Hideaki < yoshfuji @ linux - ipv6 . org >
*
* Based on net / ipv4 / xfrm4_tunnel . c
*
*/
# include <linux/module.h>
# include <linux/xfrm.h>
# include <linux/list.h>
# include <net/ip.h>
# include <net/xfrm.h>
# include <net/ipv6.h>
# include <linux/ipv6.h>
# include <linux/icmpv6.h>
2006-03-21 09:33:17 +03:00
# include <linux/mutex.h>
2005-04-17 02:20:36 +04:00
/*
2007-02-09 17:24:49 +03:00
* xfrm_tunnel_spi things are for allocating unique id ( " spi " )
2005-04-17 02:20:36 +04:00
* per xfrm_address_t .
*/
struct xfrm6_tunnel_spi {
struct hlist_node list_byaddr ;
struct hlist_node list_byspi ;
xfrm_address_t addr ;
u32 spi ;
atomic_t refcnt ;
} ;
static DEFINE_RWLOCK ( xfrm6_tunnel_spi_lock ) ;
static u32 xfrm6_tunnel_spi ;
# define XFRM6_TUNNEL_SPI_MIN 1
# define XFRM6_TUNNEL_SPI_MAX 0xffffffff
2006-12-07 07:33:20 +03:00
static struct kmem_cache * xfrm6_tunnel_spi_kmem __read_mostly ;
2005-04-17 02:20:36 +04:00
# define XFRM6_TUNNEL_SPI_BYADDR_HSIZE 256
# define XFRM6_TUNNEL_SPI_BYSPI_HSIZE 256
static struct hlist_head xfrm6_tunnel_spi_byaddr [ XFRM6_TUNNEL_SPI_BYADDR_HSIZE ] ;
static struct hlist_head xfrm6_tunnel_spi_byspi [ XFRM6_TUNNEL_SPI_BYSPI_HSIZE ] ;
2007-03-22 22:27:49 +03:00
static inline unsigned xfrm6_tunnel_spi_hash_byaddr ( xfrm_address_t * addr )
2005-04-17 02:20:36 +04:00
{
unsigned h ;
2006-11-08 11:20:21 +03:00
h = ( __force u32 ) ( addr - > a6 [ 0 ] ^ addr - > a6 [ 1 ] ^ addr - > a6 [ 2 ] ^ addr - > a6 [ 3 ] ) ;
2005-04-17 02:20:36 +04:00
h ^ = h > > 16 ;
h ^ = h > > 8 ;
h & = XFRM6_TUNNEL_SPI_BYADDR_HSIZE - 1 ;
return h ;
}
2007-03-22 22:27:49 +03:00
static inline unsigned xfrm6_tunnel_spi_hash_byspi ( u32 spi )
2005-04-17 02:20:36 +04:00
{
return spi % XFRM6_TUNNEL_SPI_BYSPI_HSIZE ;
}
static int xfrm6_tunnel_spi_init ( void )
{
int i ;
xfrm6_tunnel_spi = 0 ;
xfrm6_tunnel_spi_kmem = kmem_cache_create ( " xfrm6_tunnel_spi " ,
sizeof ( struct xfrm6_tunnel_spi ) ,
0 , SLAB_HWCACHE_ALIGN ,
2007-07-20 05:11:58 +04:00
NULL ) ;
2006-07-25 00:49:06 +04:00
if ( ! xfrm6_tunnel_spi_kmem )
2005-04-17 02:20:36 +04:00
return - ENOMEM ;
for ( i = 0 ; i < XFRM6_TUNNEL_SPI_BYADDR_HSIZE ; i + + )
INIT_HLIST_HEAD ( & xfrm6_tunnel_spi_byaddr [ i ] ) ;
for ( i = 0 ; i < XFRM6_TUNNEL_SPI_BYSPI_HSIZE ; i + + )
INIT_HLIST_HEAD ( & xfrm6_tunnel_spi_byspi [ i ] ) ;
return 0 ;
}
static void xfrm6_tunnel_spi_fini ( void )
{
int i ;
for ( i = 0 ; i < XFRM6_TUNNEL_SPI_BYADDR_HSIZE ; i + + ) {
if ( ! hlist_empty ( & xfrm6_tunnel_spi_byaddr [ i ] ) )
2006-07-25 00:49:06 +04:00
return ;
2005-04-17 02:20:36 +04:00
}
for ( i = 0 ; i < XFRM6_TUNNEL_SPI_BYSPI_HSIZE ; i + + ) {
if ( ! hlist_empty ( & xfrm6_tunnel_spi_byspi [ i ] ) )
2006-07-25 00:49:06 +04:00
return ;
2005-04-17 02:20:36 +04:00
}
kmem_cache_destroy ( xfrm6_tunnel_spi_kmem ) ;
xfrm6_tunnel_spi_kmem = NULL ;
}
static struct xfrm6_tunnel_spi * __xfrm6_tunnel_spi_lookup ( xfrm_address_t * saddr )
{
struct xfrm6_tunnel_spi * x6spi ;
struct hlist_node * pos ;
hlist_for_each_entry ( x6spi , pos ,
& xfrm6_tunnel_spi_byaddr [ xfrm6_tunnel_spi_hash_byaddr ( saddr ) ] ,
list_byaddr ) {
2006-07-25 00:49:06 +04:00
if ( memcmp ( & x6spi - > addr , saddr , sizeof ( x6spi - > addr ) ) = = 0 )
2005-04-17 02:20:36 +04:00
return x6spi ;
}
return NULL ;
}
2006-11-08 11:20:21 +03:00
__be32 xfrm6_tunnel_spi_lookup ( xfrm_address_t * saddr )
2005-04-17 02:20:36 +04:00
{
struct xfrm6_tunnel_spi * x6spi ;
u32 spi ;
read_lock_bh ( & xfrm6_tunnel_spi_lock ) ;
x6spi = __xfrm6_tunnel_spi_lookup ( saddr ) ;
spi = x6spi ? x6spi - > spi : 0 ;
read_unlock_bh ( & xfrm6_tunnel_spi_lock ) ;
2006-11-02 02:28:58 +03:00
return htonl ( spi ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( xfrm6_tunnel_spi_lookup ) ;
static u32 __xfrm6_tunnel_alloc_spi ( xfrm_address_t * saddr )
{
u32 spi ;
struct xfrm6_tunnel_spi * x6spi ;
struct hlist_node * pos ;
unsigned index ;
if ( xfrm6_tunnel_spi < XFRM6_TUNNEL_SPI_MIN | |
xfrm6_tunnel_spi > = XFRM6_TUNNEL_SPI_MAX )
xfrm6_tunnel_spi = XFRM6_TUNNEL_SPI_MIN ;
else
xfrm6_tunnel_spi + + ;
for ( spi = xfrm6_tunnel_spi ; spi < = XFRM6_TUNNEL_SPI_MAX ; spi + + ) {
index = xfrm6_tunnel_spi_hash_byspi ( spi ) ;
2007-02-09 17:24:49 +03:00
hlist_for_each_entry ( x6spi , pos ,
& xfrm6_tunnel_spi_byspi [ index ] ,
2005-04-17 02:20:36 +04:00
list_byspi ) {
if ( x6spi - > spi = = spi )
goto try_next_1 ;
}
xfrm6_tunnel_spi = spi ;
goto alloc_spi ;
try_next_1 : ;
}
for ( spi = XFRM6_TUNNEL_SPI_MIN ; spi < xfrm6_tunnel_spi ; spi + + ) {
index = xfrm6_tunnel_spi_hash_byspi ( spi ) ;
2007-02-09 17:24:49 +03:00
hlist_for_each_entry ( x6spi , pos ,
& xfrm6_tunnel_spi_byspi [ index ] ,
2005-04-17 02:20:36 +04:00
list_byspi ) {
if ( x6spi - > spi = = spi )
goto try_next_2 ;
}
xfrm6_tunnel_spi = spi ;
goto alloc_spi ;
try_next_2 : ;
}
spi = 0 ;
goto out ;
alloc_spi :
2006-12-07 07:33:16 +03:00
x6spi = kmem_cache_alloc ( xfrm6_tunnel_spi_kmem , GFP_ATOMIC ) ;
2006-07-25 00:49:06 +04:00
if ( ! x6spi )
2005-04-17 02:20:36 +04:00
goto out ;
2006-07-25 00:49:06 +04:00
2005-04-17 02:20:36 +04:00
memcpy ( & x6spi - > addr , saddr , sizeof ( x6spi - > addr ) ) ;
x6spi - > spi = spi ;
atomic_set ( & x6spi - > refcnt , 1 ) ;
hlist_add_head ( & x6spi - > list_byspi , & xfrm6_tunnel_spi_byspi [ index ] ) ;
index = xfrm6_tunnel_spi_hash_byaddr ( saddr ) ;
hlist_add_head ( & x6spi - > list_byaddr , & xfrm6_tunnel_spi_byaddr [ index ] ) ;
out :
return spi ;
}
2006-11-08 11:20:21 +03:00
__be32 xfrm6_tunnel_alloc_spi ( xfrm_address_t * saddr )
2005-04-17 02:20:36 +04:00
{
struct xfrm6_tunnel_spi * x6spi ;
u32 spi ;
write_lock_bh ( & xfrm6_tunnel_spi_lock ) ;
x6spi = __xfrm6_tunnel_spi_lookup ( saddr ) ;
if ( x6spi ) {
atomic_inc ( & x6spi - > refcnt ) ;
spi = x6spi - > spi ;
} else
spi = __xfrm6_tunnel_alloc_spi ( saddr ) ;
write_unlock_bh ( & xfrm6_tunnel_spi_lock ) ;
2006-11-02 02:28:58 +03:00
return htonl ( spi ) ;
2005-04-17 02:20:36 +04:00
}
EXPORT_SYMBOL ( xfrm6_tunnel_alloc_spi ) ;
void xfrm6_tunnel_free_spi ( xfrm_address_t * saddr )
{
struct xfrm6_tunnel_spi * x6spi ;
struct hlist_node * pos , * n ;
write_lock_bh ( & xfrm6_tunnel_spi_lock ) ;
2007-02-09 17:24:49 +03:00
hlist_for_each_entry_safe ( x6spi , pos , n ,
2005-04-17 02:20:36 +04:00
& xfrm6_tunnel_spi_byaddr [ xfrm6_tunnel_spi_hash_byaddr ( saddr ) ] ,
list_byaddr )
{
if ( memcmp ( & x6spi - > addr , saddr , sizeof ( x6spi - > addr ) ) = = 0 ) {
if ( atomic_dec_and_test ( & x6spi - > refcnt ) ) {
hlist_del ( & x6spi - > list_byaddr ) ;
hlist_del ( & x6spi - > list_byspi ) ;
kmem_cache_free ( xfrm6_tunnel_spi_kmem , x6spi ) ;
break ;
}
}
}
write_unlock_bh ( & xfrm6_tunnel_spi_lock ) ;
}
EXPORT_SYMBOL ( xfrm6_tunnel_free_spi ) ;
static int xfrm6_tunnel_output ( struct xfrm_state * x , struct sk_buff * skb )
{
2007-10-11 02:44:06 +04:00
skb_push ( skb , - skb_network_offset ( skb ) ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2006-04-01 12:52:46 +04:00
static int xfrm6_tunnel_input ( struct xfrm_state * x , struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
2007-10-18 08:28:06 +04:00
return skb_network_header ( skb ) [ IP6CB ( skb ) - > nhoff ] ;
2005-04-17 02:20:36 +04:00
}
2006-03-28 13:12:13 +04:00
static int xfrm6_tunnel_rcv ( struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
2007-04-26 04:54:47 +04:00
struct ipv6hdr * iph = ipv6_hdr ( skb ) ;
2006-09-28 05:48:18 +04:00
__be32 spi ;
2005-04-17 02:20:36 +04:00
spi = xfrm6_tunnel_spi_lookup ( ( xfrm_address_t * ) & iph - > saddr ) ;
2007-10-18 08:29:25 +04:00
return xfrm6_rcv_spi ( skb , IPPROTO_IPV6 , spi ) > 0 ? : 0 ;
2005-04-17 02:20:36 +04:00
}
2006-03-28 13:12:13 +04:00
static int xfrm6_tunnel_err ( struct sk_buff * skb , struct inet6_skb_parm * opt ,
2006-11-08 11:21:01 +03:00
int type , int code , int offset , __be32 info )
2005-04-17 02:20:36 +04:00
{
/* xfrm6_tunnel native err handling */
switch ( type ) {
2007-02-09 17:24:49 +03:00
case ICMPV6_DEST_UNREACH :
2005-04-17 02:20:36 +04:00
switch ( code ) {
2007-02-09 17:24:49 +03:00
case ICMPV6_NOROUTE :
2005-04-17 02:20:36 +04:00
case ICMPV6_ADM_PROHIBITED :
case ICMPV6_NOT_NEIGHBOUR :
case ICMPV6_ADDR_UNREACH :
case ICMPV6_PORT_UNREACH :
default :
break ;
}
break ;
case ICMPV6_PKT_TOOBIG :
break ;
case ICMPV6_TIME_EXCEED :
switch ( code ) {
case ICMPV6_EXC_HOPLIMIT :
break ;
case ICMPV6_EXC_FRAGTIME :
2007-02-09 17:24:49 +03:00
default :
2005-04-17 02:20:36 +04:00
break ;
}
break ;
case ICMPV6_PARAMPROB :
switch ( code ) {
case ICMPV6_HDR_FIELD : break ;
case ICMPV6_UNK_NEXTHDR : break ;
case ICMPV6_UNK_OPTION : break ;
}
break ;
default :
break ;
}
2006-03-28 13:12:13 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2005-06-21 00:18:08 +04:00
static int xfrm6_tunnel_init_state ( struct xfrm_state * x )
2005-04-17 02:20:36 +04:00
{
2006-09-23 02:05:15 +04:00
if ( x - > props . mode ! = XFRM_MODE_TUNNEL )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
if ( x - > encap )
return - EINVAL ;
x - > props . header_len = sizeof ( struct ipv6hdr ) ;
return 0 ;
}
static void xfrm6_tunnel_destroy ( struct xfrm_state * x )
{
xfrm6_tunnel_free_spi ( ( xfrm_address_t * ) & x - > props . saddr ) ;
}
2008-01-31 06:11:50 +03:00
static const struct xfrm_type xfrm6_tunnel_type = {
2005-04-17 02:20:36 +04:00
. description = " IP6IP6 " ,
. owner = THIS_MODULE ,
. proto = IPPROTO_IPV6 ,
. init_state = xfrm6_tunnel_init_state ,
. destructor = xfrm6_tunnel_destroy ,
. input = xfrm6_tunnel_input ,
. output = xfrm6_tunnel_output ,
} ;
2006-03-28 13:12:13 +04:00
static struct xfrm6_tunnel xfrm6_tunnel_handler = {
2005-04-17 02:20:36 +04:00
. handler = xfrm6_tunnel_rcv ,
2006-03-28 13:12:13 +04:00
. err_handler = xfrm6_tunnel_err ,
. priority = 2 ,
2005-04-17 02:20:36 +04:00
} ;
2007-02-13 23:55:55 +03:00
static struct xfrm6_tunnel xfrm46_tunnel_handler = {
. handler = xfrm6_tunnel_rcv ,
. err_handler = xfrm6_tunnel_err ,
. priority = 2 ,
} ;
2005-04-17 02:20:36 +04:00
static int __init xfrm6_tunnel_init ( void )
{
2006-07-25 00:49:06 +04:00
if ( xfrm_register_type ( & xfrm6_tunnel_type , AF_INET6 ) < 0 )
2005-04-17 02:20:36 +04:00
return - EAGAIN ;
2006-07-25 00:49:06 +04:00
2007-02-13 23:55:55 +03:00
if ( xfrm6_tunnel_register ( & xfrm6_tunnel_handler , AF_INET6 ) ) {
xfrm_unregister_type ( & xfrm6_tunnel_type , AF_INET6 ) ;
return - EAGAIN ;
}
if ( xfrm6_tunnel_register ( & xfrm46_tunnel_handler , AF_INET ) ) {
xfrm6_tunnel_deregister ( & xfrm6_tunnel_handler , AF_INET6 ) ;
2005-04-17 02:20:36 +04:00
xfrm_unregister_type ( & xfrm6_tunnel_type , AF_INET6 ) ;
return - EAGAIN ;
}
if ( xfrm6_tunnel_spi_init ( ) < 0 ) {
2007-02-13 23:55:55 +03:00
xfrm6_tunnel_deregister ( & xfrm46_tunnel_handler , AF_INET ) ;
xfrm6_tunnel_deregister ( & xfrm6_tunnel_handler , AF_INET6 ) ;
2005-04-17 02:20:36 +04:00
xfrm_unregister_type ( & xfrm6_tunnel_type , AF_INET6 ) ;
return - EAGAIN ;
}
return 0 ;
}
static void __exit xfrm6_tunnel_fini ( void )
{
xfrm6_tunnel_spi_fini ( ) ;
2007-02-13 23:55:55 +03:00
xfrm6_tunnel_deregister ( & xfrm46_tunnel_handler , AF_INET ) ;
xfrm6_tunnel_deregister ( & xfrm6_tunnel_handler , AF_INET6 ) ;
2006-07-25 00:49:06 +04:00
xfrm_unregister_type ( & xfrm6_tunnel_type , AF_INET6 ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( xfrm6_tunnel_init ) ;
module_exit ( xfrm6_tunnel_fini ) ;
MODULE_LICENSE ( " GPL " ) ;
2007-06-27 10:57:49 +04:00
MODULE_ALIAS_XFRM_TYPE ( AF_INET6 , XFRM_PROTO_IPV6 ) ;