2005-04-17 02:20:36 +04:00
/*
2007-02-14 18:43:16 +03:00
* IPv6 tunneling device
2005-04-17 02:20:36 +04:00
* Linux INET6 implementation
*
* Authors :
2007-02-09 17:24:49 +03:00
* Ville Nuorvala < vnuorval @ tcs . hut . fi >
2007-02-14 18:43:16 +03:00
* Yasuyuki Kozakai < kozakai @ linux - ipv6 . org >
2005-04-17 02:20:36 +04:00
*
* Based on :
2007-02-14 18:43:16 +03:00
* linux / net / ipv6 / sit . c and linux / net / ipv4 / ipip . c
2005-04-17 02:20:36 +04:00
*
* RFC 2473
*
* 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>
2006-01-11 23:17:47 +03:00
# include <linux/capability.h>
2005-04-17 02:20:36 +04:00
# include <linux/errno.h>
# include <linux/types.h>
# include <linux/sockios.h>
2007-02-14 18:43:16 +03:00
# include <linux/icmp.h>
2005-04-17 02:20:36 +04:00
# include <linux/if.h>
# include <linux/in.h>
# include <linux/ip.h>
# include <linux/if_tunnel.h>
# include <linux/net.h>
# include <linux/in6.h>
# include <linux/netdevice.h>
# include <linux/if_arp.h>
# include <linux/icmpv6.h>
# include <linux/init.h>
# include <linux/route.h>
# include <linux/rtnetlink.h>
# include <linux/netfilter_ipv6.h>
# include <asm/uaccess.h>
# include <asm/atomic.h>
2007-02-14 18:43:16 +03:00
# include <net/icmp.h>
2005-04-17 02:20:36 +04:00
# include <net/ip.h>
# include <net/ipv6.h>
# include <net/ip6_route.h>
# include <net/addrconf.h>
# include <net/ip6_tunnel.h>
# include <net/xfrm.h>
# include <net/dsfield.h>
# include <net/inet_ecn.h>
2008-04-16 12:22:02 +04:00
# include <net/net_namespace.h>
# include <net/netns/generic.h>
2005-04-17 02:20:36 +04:00
MODULE_AUTHOR ( " Ville Nuorvala " ) ;
2007-02-14 18:43:16 +03:00
MODULE_DESCRIPTION ( " IPv6 tunneling device " ) ;
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;
# define IPV6_TLV_TEL_DST_SIZE 8
# ifdef IP6_TNL_DEBUG
2008-03-06 07:47:47 +03:00
# define IP6_TNL_TRACE(x...) printk(KERN_DEBUG "%s:" x "\n", __func__)
2005-04-17 02:20:36 +04:00
# else
# define IP6_TNL_TRACE(x...) do {;} while(0)
# endif
# define IPV6_TCLASS_MASK (IPV6_FLOWINFO_MASK & ~IPV6_FLOWLABEL_MASK)
2007-02-14 18:43:16 +03:00
# define IPV6_TCLASS_SHIFT 20
2005-04-17 02:20:36 +04:00
# define HASH_SIZE 32
2006-11-15 07:56:00 +03:00
# define HASH(addr) ((__force u32)((addr)->s6_addr32[0] ^ (addr)->s6_addr32[1] ^ \
2007-02-09 17:24:49 +03:00
( addr ) - > s6_addr32 [ 2 ] ^ ( addr ) - > s6_addr32 [ 3 ] ) & \
( HASH_SIZE - 1 ) )
2005-04-17 02:20:36 +04:00
2007-02-09 18:30:33 +03:00
static int ip6_fb_tnl_dev_init ( struct net_device * dev ) ;
static int ip6_tnl_dev_init ( struct net_device * dev ) ;
static void ip6_tnl_dev_setup ( struct net_device * dev ) ;
2005-04-17 02:20:36 +04:00
2008-04-16 12:22:02 +04:00
static int ip6_tnl_net_id ;
struct ip6_tnl_net {
2008-04-16 12:23:02 +04:00
/* the IPv6 tunnel fallback device */
struct net_device * fb_tnl_dev ;
2008-04-16 12:23:22 +04:00
/* lists for storing tunnels in use */
struct ip6_tnl * tnls_r_l [ HASH_SIZE ] ;
struct ip6_tnl * tnls_wc [ 1 ] ;
struct ip6_tnl * * tnls [ 2 ] ;
2008-04-16 12:22:02 +04:00
} ;
2005-04-17 02:20:36 +04:00
/* lock for the tunnel lists */
2007-02-09 18:30:33 +03:00
static DEFINE_RWLOCK ( ip6_tnl_lock ) ;
2005-04-17 02:20:36 +04:00
static inline struct dst_entry * ip6_tnl_dst_check ( struct ip6_tnl * t )
{
struct dst_entry * dst = t - > dst_cache ;
2007-02-09 17:24:49 +03:00
if ( dst & & dst - > obsolete & &
2005-04-17 02:20:36 +04:00
dst - > ops - > check ( dst , t - > dst_cookie ) = = NULL ) {
t - > dst_cache = NULL ;
dst_release ( dst ) ;
return NULL ;
}
return dst ;
}
static inline void ip6_tnl_dst_reset ( struct ip6_tnl * t )
{
dst_release ( t - > dst_cache ) ;
t - > dst_cache = NULL ;
}
static inline void ip6_tnl_dst_store ( struct ip6_tnl * t , struct dst_entry * dst )
{
struct rt6_info * rt = ( struct rt6_info * ) dst ;
t - > dst_cookie = rt - > rt6i_node ? rt - > rt6i_node - > fn_sernum : 0 ;
dst_release ( t - > dst_cache ) ;
t - > dst_cache = dst ;
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_lookup - fetch tunnel matching the end - point addresses
2007-02-09 17:24:49 +03:00
* @ remote : the address of the tunnel exit - point
* @ local : the address of the tunnel entry - point
2005-04-17 02:20:36 +04:00
*
2007-02-09 17:24:49 +03:00
* Return :
2005-04-17 02:20:36 +04:00
* tunnel matching given end - points if found ,
2007-02-09 17:24:49 +03:00
* else fallback tunnel if its device is up ,
2005-04-17 02:20:36 +04:00
* else % NULL
* */
static struct ip6_tnl *
2008-04-16 12:22:23 +04:00
ip6_tnl_lookup ( struct net * net , struct in6_addr * remote , struct in6_addr * local )
2005-04-17 02:20:36 +04:00
{
unsigned h0 = HASH ( remote ) ;
unsigned h1 = HASH ( local ) ;
struct ip6_tnl * t ;
2008-04-16 12:23:22 +04:00
struct ip6_tnl_net * ip6n = net_generic ( net , ip6_tnl_net_id ) ;
2005-04-17 02:20:36 +04:00
2008-04-16 12:23:22 +04:00
for ( t = ip6n - > tnls_r_l [ h0 ^ h1 ] ; t ; t = t - > next ) {
2005-04-17 02:20:36 +04:00
if ( ipv6_addr_equal ( local , & t - > parms . laddr ) & &
ipv6_addr_equal ( remote , & t - > parms . raddr ) & &
( t - > dev - > flags & IFF_UP ) )
return t ;
}
2008-04-16 12:23:22 +04:00
if ( ( t = ip6n - > tnls_wc [ 0 ] ) ! = NULL & & ( t - > dev - > flags & IFF_UP ) )
2005-04-17 02:20:36 +04:00
return t ;
return NULL ;
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_bucket - get head of list matching given tunnel parameters
2007-02-09 17:24:49 +03:00
* @ p : parameters containing tunnel end - points
2005-04-17 02:20:36 +04:00
*
* Description :
2007-02-09 18:30:33 +03:00
* ip6_tnl_bucket ( ) returns the head of the list matching the
2005-04-17 02:20:36 +04:00
* & struct in6_addr entries laddr and raddr in @ p .
*
2007-02-09 17:24:49 +03:00
* Return : head of IPv6 tunnel list
2005-04-17 02:20:36 +04:00
* */
static struct ip6_tnl * *
2008-04-16 12:22:23 +04:00
ip6_tnl_bucket ( struct ip6_tnl_net * ip6n , struct ip6_tnl_parm * p )
2005-04-17 02:20:36 +04:00
{
struct in6_addr * remote = & p - > raddr ;
struct in6_addr * local = & p - > laddr ;
unsigned h = 0 ;
int prio = 0 ;
if ( ! ipv6_addr_any ( remote ) | | ! ipv6_addr_any ( local ) ) {
prio = 1 ;
h = HASH ( remote ) ^ HASH ( local ) ;
}
2008-04-16 12:23:22 +04:00
return & ip6n - > tnls [ prio ] [ h ] ;
2005-04-17 02:20:36 +04:00
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_link - add tunnel to hash table
2005-04-17 02:20:36 +04:00
* @ t : tunnel to be added
* */
static void
2008-04-16 12:22:23 +04:00
ip6_tnl_link ( struct ip6_tnl_net * ip6n , struct ip6_tnl * t )
2005-04-17 02:20:36 +04:00
{
2008-04-16 12:22:23 +04:00
struct ip6_tnl * * tp = ip6_tnl_bucket ( ip6n , & t - > parms ) ;
2005-04-17 02:20:36 +04:00
t - > next = * tp ;
2007-02-09 18:30:33 +03:00
write_lock_bh ( & ip6_tnl_lock ) ;
2005-04-17 02:20:36 +04:00
* tp = t ;
2007-02-09 18:30:33 +03:00
write_unlock_bh ( & ip6_tnl_lock ) ;
2005-04-17 02:20:36 +04:00
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_unlink - remove tunnel from hash table
2005-04-17 02:20:36 +04:00
* @ t : tunnel to be removed
* */
static void
2008-04-16 12:22:23 +04:00
ip6_tnl_unlink ( struct ip6_tnl_net * ip6n , struct ip6_tnl * t )
2005-04-17 02:20:36 +04:00
{
struct ip6_tnl * * tp ;
2008-04-16 12:22:23 +04:00
for ( tp = ip6_tnl_bucket ( ip6n , & t - > parms ) ; * tp ; tp = & ( * tp ) - > next ) {
2005-04-17 02:20:36 +04:00
if ( t = = * tp ) {
2007-02-09 18:30:33 +03:00
write_lock_bh ( & ip6_tnl_lock ) ;
2005-04-17 02:20:36 +04:00
* tp = t - > next ;
2007-02-09 18:30:33 +03:00
write_unlock_bh ( & ip6_tnl_lock ) ;
2005-04-17 02:20:36 +04:00
break ;
}
}
}
/**
* ip6_tnl_create ( ) - create a new tunnel
* @ p : tunnel parameters
* @ pt : pointer to new tunnel
*
* Description :
* Create tunnel matching given parameters .
2007-02-09 17:24:49 +03:00
*
* Return :
2006-11-25 04:05:41 +03:00
* created tunnel or NULL
2005-04-17 02:20:36 +04:00
* */
2008-04-16 12:22:23 +04:00
static struct ip6_tnl * ip6_tnl_create ( struct net * net , struct ip6_tnl_parm * p )
2005-04-17 02:20:36 +04:00
{
struct net_device * dev ;
struct ip6_tnl * t ;
char name [ IFNAMSIZ ] ;
int err ;
2008-04-16 12:22:23 +04:00
struct ip6_tnl_net * ip6n = net_generic ( net , ip6_tnl_net_id ) ;
2005-04-17 02:20:36 +04:00
2008-02-24 07:19:20 +03:00
if ( p - > name [ 0 ] )
2005-04-17 02:20:36 +04:00
strlcpy ( name , p - > name , IFNAMSIZ ) ;
2008-02-24 07:19:20 +03:00
else
sprintf ( name , " ip6tnl%%d " ) ;
2007-02-09 18:30:33 +03:00
dev = alloc_netdev ( sizeof ( * t ) , name , ip6_tnl_dev_setup ) ;
2005-04-17 02:20:36 +04:00
if ( dev = = NULL )
2006-11-25 04:05:41 +03:00
goto failed ;
2005-04-17 02:20:36 +04:00
2008-04-16 12:24:13 +04:00
dev_net_set ( dev , net ) ;
[INET]: Don't create tunnels with '%' in name.
Four tunnel drivers (ip_gre, ipip, ip6_tunnel and sit) can receive a
pre-defined name for a device from the userspace. Since these drivers
call the register_netdevice() (rtnl_lock, is held), which does _not_
generate the device's name, this name may contain a '%' character.
Not sure how bad is this to have a device with a '%' in its name, but
all the other places either use the register_netdev(), which call the
dev_alloc_name(), or explicitly call the dev_alloc_name() before
registering, i.e. do not allow for such names.
This had to be prior to the commit 34cc7b, but I forgot to number the
patches and this one got lost, sorry.
Signed-off-by: Pavel Emelyanov <xemul@openvz.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2008-02-27 10:51:04 +03:00
if ( strchr ( name , ' % ' ) ) {
if ( dev_alloc_name ( dev , name ) < 0 )
goto failed_free ;
}
2006-01-09 09:05:26 +03:00
t = netdev_priv ( dev ) ;
2007-02-09 18:30:33 +03:00
dev - > init = ip6_tnl_dev_init ;
2005-04-17 02:20:36 +04:00
t - > parms = * p ;
[INET]: Don't create tunnels with '%' in name.
Four tunnel drivers (ip_gre, ipip, ip6_tunnel and sit) can receive a
pre-defined name for a device from the userspace. Since these drivers
call the register_netdevice() (rtnl_lock, is held), which does _not_
generate the device's name, this name may contain a '%' character.
Not sure how bad is this to have a device with a '%' in its name, but
all the other places either use the register_netdev(), which call the
dev_alloc_name(), or explicitly call the dev_alloc_name() before
registering, i.e. do not allow for such names.
This had to be prior to the commit 34cc7b, but I forgot to number the
patches and this one got lost, sorry.
Signed-off-by: Pavel Emelyanov <xemul@openvz.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2008-02-27 10:51:04 +03:00
if ( ( err = register_netdevice ( dev ) ) < 0 )
goto failed_free ;
2005-04-17 02:20:36 +04:00
dev_hold ( dev ) ;
2008-04-16 12:22:23 +04:00
ip6_tnl_link ( ip6n , t ) ;
2006-11-25 04:05:41 +03:00
return t ;
[INET]: Don't create tunnels with '%' in name.
Four tunnel drivers (ip_gre, ipip, ip6_tunnel and sit) can receive a
pre-defined name for a device from the userspace. Since these drivers
call the register_netdevice() (rtnl_lock, is held), which does _not_
generate the device's name, this name may contain a '%' character.
Not sure how bad is this to have a device with a '%' in its name, but
all the other places either use the register_netdev(), which call the
dev_alloc_name(), or explicitly call the dev_alloc_name() before
registering, i.e. do not allow for such names.
This had to be prior to the commit 34cc7b, but I forgot to number the
patches and this one got lost, sorry.
Signed-off-by: Pavel Emelyanov <xemul@openvz.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
2008-02-27 10:51:04 +03:00
failed_free :
free_netdev ( dev ) ;
2006-11-25 04:05:41 +03:00
failed :
return NULL ;
2005-04-17 02:20:36 +04:00
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_locate - find or create tunnel matching given parameters
2007-02-09 17:24:49 +03:00
* @ p : tunnel parameters
2005-04-17 02:20:36 +04:00
* @ create : ! = 0 if allowed to create new tunnel if no match found
*
* Description :
2007-02-09 18:30:33 +03:00
* ip6_tnl_locate ( ) first tries to locate an existing tunnel
2005-04-17 02:20:36 +04:00
* based on @ parms . If this is unsuccessful , but @ create is set a new
* tunnel device is created and registered for use .
*
* Return :
2006-11-25 04:05:41 +03:00
* matching tunnel or NULL
2005-04-17 02:20:36 +04:00
* */
2008-04-16 12:22:23 +04:00
static struct ip6_tnl * ip6_tnl_locate ( struct net * net ,
struct ip6_tnl_parm * p , int create )
2005-04-17 02:20:36 +04:00
{
struct in6_addr * remote = & p - > raddr ;
struct in6_addr * local = & p - > laddr ;
struct ip6_tnl * t ;
2008-04-16 12:22:23 +04:00
struct ip6_tnl_net * ip6n = net_generic ( net , ip6_tnl_net_id ) ;
2005-04-17 02:20:36 +04:00
2008-04-16 12:22:23 +04:00
for ( t = * ip6_tnl_bucket ( ip6n , p ) ; t ; t = t - > next ) {
2005-04-17 02:20:36 +04:00
if ( ipv6_addr_equal ( local , & t - > parms . laddr ) & &
2006-11-25 04:05:41 +03:00
ipv6_addr_equal ( remote , & t - > parms . raddr ) )
return t ;
2005-04-17 02:20:36 +04:00
}
if ( ! create )
2006-11-25 04:05:41 +03:00
return NULL ;
2008-04-16 12:22:23 +04:00
return ip6_tnl_create ( net , p ) ;
2005-04-17 02:20:36 +04:00
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_dev_uninit - tunnel device uninitializer
2005-04-17 02:20:36 +04:00
* @ dev : the device to be destroyed
2007-02-09 17:24:49 +03:00
*
2005-04-17 02:20:36 +04:00
* Description :
2007-02-09 18:30:33 +03:00
* ip6_tnl_dev_uninit ( ) removes tunnel from its list
2005-04-17 02:20:36 +04:00
* */
static void
2007-02-09 18:30:33 +03:00
ip6_tnl_dev_uninit ( struct net_device * dev )
2005-04-17 02:20:36 +04:00
{
2006-01-09 09:05:26 +03:00
struct ip6_tnl * t = netdev_priv ( dev ) ;
2008-04-16 12:22:23 +04:00
struct net * net = dev_net ( dev ) ;
struct ip6_tnl_net * ip6n = net_generic ( net , ip6_tnl_net_id ) ;
2005-04-17 02:20:36 +04:00
2008-04-16 12:23:02 +04:00
if ( dev = = ip6n - > fb_tnl_dev ) {
2007-02-09 18:30:33 +03:00
write_lock_bh ( & ip6_tnl_lock ) ;
2008-04-16 12:23:22 +04:00
ip6n - > tnls_wc [ 0 ] = NULL ;
2007-02-09 18:30:33 +03:00
write_unlock_bh ( & ip6_tnl_lock ) ;
2005-04-17 02:20:36 +04:00
} else {
2008-04-16 12:22:23 +04:00
ip6_tnl_unlink ( ip6n , t ) ;
2005-04-17 02:20:36 +04:00
}
ip6_tnl_dst_reset ( t ) ;
dev_put ( dev ) ;
}
/**
* parse_tvl_tnl_enc_lim - handle encapsulation limit option
* @ skb : received socket buffer
*
2007-02-09 17:24:49 +03:00
* Return :
* 0 if none was found ,
2005-04-17 02:20:36 +04:00
* else index to encapsulation limit
* */
static __u16
parse_tlv_tnl_enc_lim ( struct sk_buff * skb , __u8 * raw )
{
struct ipv6hdr * ipv6h = ( struct ipv6hdr * ) raw ;
__u8 nexthdr = ipv6h - > nexthdr ;
__u16 off = sizeof ( * ipv6h ) ;
while ( ipv6_ext_hdr ( nexthdr ) & & nexthdr ! = NEXTHDR_NONE ) {
__u16 optlen = 0 ;
struct ipv6_opt_hdr * hdr ;
if ( raw + off + sizeof ( * hdr ) > skb - > data & &
! pskb_may_pull ( skb , raw - skb - > data + off + sizeof ( * hdr ) ) )
break ;
hdr = ( struct ipv6_opt_hdr * ) ( raw + off ) ;
if ( nexthdr = = NEXTHDR_FRAGMENT ) {
struct frag_hdr * frag_hdr = ( struct frag_hdr * ) hdr ;
if ( frag_hdr - > frag_off )
break ;
optlen = 8 ;
} else if ( nexthdr = = NEXTHDR_AUTH ) {
optlen = ( hdr - > hdrlen + 2 ) < < 2 ;
} else {
optlen = ipv6_optlen ( hdr ) ;
}
if ( nexthdr = = NEXTHDR_DEST ) {
__u16 i = off + 2 ;
while ( 1 ) {
struct ipv6_tlv_tnl_enc_lim * tel ;
/* No more room for encapsulation limit */
if ( i + sizeof ( * tel ) > off + optlen )
break ;
tel = ( struct ipv6_tlv_tnl_enc_lim * ) & raw [ i ] ;
/* return index of option if found and valid */
if ( tel - > type = = IPV6_TLV_TNL_ENCAP_LIMIT & &
tel - > length = = 1 )
return i ;
/* else jump to next option */
if ( tel - > type )
i + = tel - > length + 2 ;
else
i + + ;
}
}
nexthdr = hdr - > nexthdr ;
off + = optlen ;
}
return 0 ;
}
/**
2006-10-31 17:11:25 +03:00
* ip6_tnl_err - tunnel error handler
2005-04-17 02:20:36 +04:00
*
* Description :
2006-10-31 17:11:25 +03:00
* ip6_tnl_err ( ) should handle errors in the tunnel according
2005-04-17 02:20:36 +04:00
* to the specifications in RFC 2473.
* */
2006-03-28 13:12:13 +04:00
static int
2006-11-30 08:43:28 +03:00
ip6_tnl_err ( struct sk_buff * skb , __u8 ipproto , struct inet6_skb_parm * opt ,
2007-07-26 20:33:29 +04:00
int * type , int * code , int * msg , __u32 * info , int offset )
2005-04-17 02:20:36 +04:00
{
struct ipv6hdr * ipv6h = ( struct ipv6hdr * ) skb - > data ;
struct ip6_tnl * t ;
int rel_msg = 0 ;
int rel_type = ICMPV6_DEST_UNREACH ;
int rel_code = ICMPV6_ADDR_UNREACH ;
__u32 rel_info = 0 ;
__u16 len ;
2006-03-28 13:12:13 +04:00
int err = - ENOENT ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:49 +03:00
/* If the packet doesn't contain the original IPv6 header we are
in trouble since we might need the source address for further
2005-04-17 02:20:36 +04:00
processing of the error . */
2007-02-09 18:30:33 +03:00
read_lock ( & ip6_tnl_lock ) ;
2008-04-16 12:22:43 +04:00
if ( ( t = ip6_tnl_lookup ( dev_net ( skb - > dev ) , & ipv6h - > daddr ,
2008-04-16 12:22:23 +04:00
& ipv6h - > saddr ) ) = = NULL )
2005-04-17 02:20:36 +04:00
goto out ;
2006-11-30 08:43:28 +03:00
if ( t - > parms . proto ! = ipproto & & t - > parms . proto ! = 0 )
goto out ;
2006-03-28 13:12:13 +04:00
err = 0 ;
2006-10-31 17:11:25 +03:00
switch ( * type ) {
2005-04-17 02:20:36 +04:00
__u32 teli ;
struct ipv6_tlv_tnl_enc_lim * tel ;
__u32 mtu ;
case ICMPV6_DEST_UNREACH :
if ( net_ratelimit ( ) )
printk ( KERN_WARNING
" %s: Path to destination invalid "
" or inactive! \n " , t - > parms . name ) ;
rel_msg = 1 ;
break ;
case ICMPV6_TIME_EXCEED :
2006-10-31 17:11:25 +03:00
if ( ( * code ) = = ICMPV6_EXC_HOPLIMIT ) {
2005-04-17 02:20:36 +04:00
if ( net_ratelimit ( ) )
printk ( KERN_WARNING
" %s: Too small hop limit or "
2007-02-09 17:24:49 +03:00
" routing loop in tunnel! \n " ,
2005-04-17 02:20:36 +04:00
t - > parms . name ) ;
rel_msg = 1 ;
}
break ;
case ICMPV6_PARAMPROB :
2006-11-25 04:08:58 +03:00
teli = 0 ;
2006-10-31 17:11:25 +03:00
if ( ( * code ) = = ICMPV6_HDR_FIELD )
2006-11-25 04:08:58 +03:00
teli = parse_tlv_tnl_enc_lim ( skb , skb - > data ) ;
2005-04-17 02:20:36 +04:00
2007-07-26 20:33:29 +04:00
if ( teli & & teli = = * info - 2 ) {
2005-04-17 02:20:36 +04:00
tel = ( struct ipv6_tlv_tnl_enc_lim * ) & skb - > data [ teli ] ;
if ( tel - > encap_limit = = 0 ) {
if ( net_ratelimit ( ) )
printk ( KERN_WARNING
" %s: Too small encapsulation "
" limit or routing loop in "
" tunnel! \n " , t - > parms . name ) ;
rel_msg = 1 ;
}
2006-11-25 04:08:58 +03:00
} else if ( net_ratelimit ( ) ) {
printk ( KERN_WARNING
" %s: Recipient unable to parse tunneled "
" packet! \n " , t - > parms . name ) ;
2005-04-17 02:20:36 +04:00
}
break ;
case ICMPV6_PKT_TOOBIG :
2007-07-26 20:33:29 +04:00
mtu = * info - offset ;
2005-04-17 02:20:36 +04:00
if ( mtu < IPV6_MIN_MTU )
mtu = IPV6_MIN_MTU ;
t - > dev - > mtu = mtu ;
2006-02-19 00:02:18 +03:00
if ( ( len = sizeof ( * ipv6h ) + ntohs ( ipv6h - > payload_len ) ) > mtu ) {
2005-04-17 02:20:36 +04:00
rel_type = ICMPV6_PKT_TOOBIG ;
rel_code = 0 ;
rel_info = mtu ;
rel_msg = 1 ;
}
break ;
}
2006-10-31 17:11:25 +03:00
* type = rel_type ;
* code = rel_code ;
* info = rel_info ;
* msg = rel_msg ;
out :
2007-02-09 18:30:33 +03:00
read_unlock ( & ip6_tnl_lock ) ;
2006-10-31 17:11:25 +03:00
return err ;
}
2007-02-14 18:43:16 +03:00
static int
ip4ip6_err ( struct sk_buff * skb , struct inet6_skb_parm * opt ,
2007-07-26 20:33:29 +04:00
int type , int code , int offset , __be32 info )
2007-02-14 18:43:16 +03:00
{
int rel_msg = 0 ;
int rel_type = type ;
int rel_code = code ;
2007-07-26 20:33:29 +04:00
__u32 rel_info = ntohl ( info ) ;
2007-02-14 18:43:16 +03:00
int err ;
struct sk_buff * skb2 ;
struct iphdr * eiph ;
struct flowi fl ;
struct rtable * rt ;
2006-11-30 08:43:28 +03:00
err = ip6_tnl_err ( skb , IPPROTO_IPIP , opt , & rel_type , & rel_code ,
& rel_msg , & rel_info , offset ) ;
2007-02-14 18:43:16 +03:00
if ( err < 0 )
return err ;
if ( rel_msg = = 0 )
return 0 ;
switch ( rel_type ) {
case ICMPV6_DEST_UNREACH :
if ( rel_code ! = ICMPV6_ADDR_UNREACH )
return 0 ;
rel_type = ICMP_DEST_UNREACH ;
rel_code = ICMP_HOST_UNREACH ;
break ;
case ICMPV6_PKT_TOOBIG :
if ( rel_code ! = 0 )
return 0 ;
rel_type = ICMP_DEST_UNREACH ;
rel_code = ICMP_FRAG_NEEDED ;
break ;
default :
return 0 ;
}
if ( ! pskb_may_pull ( skb , offset + sizeof ( struct iphdr ) ) )
return 0 ;
skb2 = skb_clone ( skb , GFP_ATOMIC ) ;
if ( ! skb2 )
return 0 ;
dst_release ( skb2 - > dst ) ;
skb2 - > dst = NULL ;
skb_pull ( skb2 , offset ) ;
2007-04-11 07:45:18 +04:00
skb_reset_network_header ( skb2 ) ;
2007-04-21 09:47:35 +04:00
eiph = ip_hdr ( skb2 ) ;
2007-02-14 18:43:16 +03:00
/* Try to guess incoming interface */
memset ( & fl , 0 , sizeof ( fl ) ) ;
fl . fl4_dst = eiph - > saddr ;
fl . fl4_tos = RT_TOS ( eiph - > tos ) ;
fl . proto = IPPROTO_IPIP ;
2008-04-16 12:23:44 +04:00
if ( ip_route_output_key ( dev_net ( skb - > dev ) , & rt , & fl ) )
2007-02-14 18:43:16 +03:00
goto out ;
skb2 - > dev = rt - > u . dst . dev ;
/* route "incoming" packet */
if ( rt - > rt_flags & RTCF_LOCAL ) {
ip_rt_put ( rt ) ;
rt = NULL ;
fl . fl4_dst = eiph - > daddr ;
fl . fl4_src = eiph - > saddr ;
fl . fl4_tos = eiph - > tos ;
2008-04-16 12:23:44 +04:00
if ( ip_route_output_key ( dev_net ( skb - > dev ) , & rt , & fl ) | |
2007-02-14 18:43:16 +03:00
rt - > u . dst . dev - > type ! = ARPHRD_TUNNEL ) {
ip_rt_put ( rt ) ;
goto out ;
}
2008-02-19 07:49:36 +03:00
skb2 - > dst = ( struct dst_entry * ) rt ;
2007-02-14 18:43:16 +03:00
} else {
ip_rt_put ( rt ) ;
if ( ip_route_input ( skb2 , eiph - > daddr , eiph - > saddr , eiph - > tos ,
skb2 - > dev ) | |
skb2 - > dst - > dev - > type ! = ARPHRD_TUNNEL )
goto out ;
}
/* change mtu on this route */
if ( rel_type = = ICMP_DEST_UNREACH & & rel_code = = ICMP_FRAG_NEEDED ) {
if ( rel_info > dst_mtu ( skb2 - > dst ) )
goto out ;
skb2 - > dst - > ops - > update_pmtu ( skb2 - > dst , rel_info ) ;
}
2007-07-26 20:33:29 +04:00
icmp_send ( skb2 , rel_type , rel_code , htonl ( rel_info ) ) ;
2007-02-14 18:43:16 +03:00
out :
kfree_skb ( skb2 ) ;
return 0 ;
}
2006-10-31 17:11:25 +03:00
static int
ip6ip6_err ( struct sk_buff * skb , struct inet6_skb_parm * opt ,
2007-07-26 20:33:29 +04:00
int type , int code , int offset , __be32 info )
2006-10-31 17:11:25 +03:00
{
int rel_msg = 0 ;
int rel_type = type ;
int rel_code = code ;
2007-07-26 20:33:29 +04:00
__u32 rel_info = ntohl ( info ) ;
2006-10-31 17:11:25 +03:00
int err ;
2006-11-30 08:43:28 +03:00
err = ip6_tnl_err ( skb , IPPROTO_IPV6 , opt , & rel_type , & rel_code ,
& rel_msg , & rel_info , offset ) ;
2006-10-31 17:11:25 +03:00
if ( err < 0 )
return err ;
if ( rel_msg & & pskb_may_pull ( skb , offset + sizeof ( struct ipv6hdr ) ) ) {
2005-04-17 02:20:36 +04:00
struct rt6_info * rt ;
struct sk_buff * skb2 = skb_clone ( skb , GFP_ATOMIC ) ;
2006-11-25 04:06:53 +03:00
2005-04-17 02:20:36 +04:00
if ( ! skb2 )
2006-10-31 17:11:25 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
dst_release ( skb2 - > dst ) ;
skb2 - > dst = NULL ;
skb_pull ( skb2 , offset ) ;
2007-04-11 07:45:18 +04:00
skb_reset_network_header ( skb2 ) ;
2005-04-17 02:20:36 +04:00
/* Try to guess incoming interface */
2008-04-16 12:23:44 +04:00
rt = rt6_lookup ( dev_net ( skb - > dev ) , & ipv6_hdr ( skb2 ) - > saddr ,
NULL , 0 , 0 ) ;
2005-04-17 02:20:36 +04:00
if ( rt & & rt - > rt6i_dev )
skb2 - > dev = rt - > rt6i_dev ;
icmpv6_send ( skb2 , rel_type , rel_code , rel_info , skb2 - > dev ) ;
if ( rt )
dst_release ( & rt - > u . dst ) ;
kfree_skb ( skb2 ) ;
}
2006-10-31 17:11:25 +03:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2007-02-14 18:43:16 +03:00
static void ip4ip6_dscp_ecn_decapsulate ( struct ip6_tnl * t ,
struct ipv6hdr * ipv6h ,
struct sk_buff * skb )
{
__u8 dsfield = ipv6_get_dsfield ( ipv6h ) & ~ INET_ECN_MASK ;
if ( t - > parms . flags & IP6_TNL_F_RCV_DSCP_COPY )
2007-04-21 09:47:35 +04:00
ipv4_change_dsfield ( ip_hdr ( skb ) , INET_ECN_MASK , dsfield ) ;
2007-02-14 18:43:16 +03:00
if ( INET_ECN_is_ce ( dsfield ) )
2007-04-21 09:47:35 +04:00
IP_ECN_set_ce ( ip_hdr ( skb ) ) ;
2007-02-14 18:43:16 +03:00
}
2006-11-03 03:39:14 +03:00
static void ip6ip6_dscp_ecn_decapsulate ( struct ip6_tnl * t ,
struct ipv6hdr * ipv6h ,
struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
2006-11-03 03:39:14 +03:00
if ( t - > parms . flags & IP6_TNL_F_RCV_DSCP_COPY )
2007-11-14 08:40:13 +03:00
ipv6_copy_dscp ( ipv6_get_dsfield ( ipv6h ) , ipv6_hdr ( skb ) ) ;
2005-04-17 02:20:36 +04:00
2006-11-03 03:39:14 +03:00
if ( INET_ECN_is_ce ( ipv6_get_dsfield ( ipv6h ) ) )
2007-04-26 04:54:47 +04:00
IP6_ECN_set_ce ( ipv6_hdr ( skb ) ) ;
2005-04-17 02:20:36 +04:00
}
2006-11-03 03:39:14 +03:00
2006-11-25 04:06:27 +03:00
static inline int ip6_tnl_rcv_ctl ( struct ip6_tnl * t )
{
struct ip6_tnl_parm * p = & t - > parms ;
int ret = 0 ;
2008-04-16 12:23:44 +04:00
struct net * net = dev_net ( t - > dev ) ;
2006-11-25 04:06:27 +03:00
if ( p - > flags & IP6_TNL_F_CAP_RCV ) {
2007-02-09 17:24:49 +03:00
struct net_device * ldev = NULL ;
2006-11-25 04:06:27 +03:00
if ( p - > link )
2008-04-16 12:23:44 +04:00
ldev = dev_get_by_index ( net , p - > link ) ;
2006-11-25 04:06:27 +03:00
if ( ( ipv6_addr_is_multicast ( & p - > laddr ) | |
2008-04-16 12:23:44 +04:00
likely ( ipv6_chk_addr ( net , & p - > laddr , ldev , 0 ) ) ) & &
likely ( ! ipv6_chk_addr ( net , & p - > raddr , NULL , 0 ) ) )
2006-11-25 04:06:27 +03:00
ret = 1 ;
if ( ldev )
dev_put ( ldev ) ;
}
return ret ;
}
2005-04-17 02:20:36 +04:00
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_rcv - decapsulate IPv6 packet and retransmit it locally
2005-04-17 02:20:36 +04:00
* @ skb : received socket buffer
2006-11-03 03:39:14 +03:00
* @ protocol : ethernet protocol ID
* @ dscp_ecn_decapsulate : the function to decapsulate DSCP code and ECN
2005-04-17 02:20:36 +04:00
*
* Return : 0
* */
2006-11-03 03:39:14 +03:00
static int ip6_tnl_rcv ( struct sk_buff * skb , __u16 protocol ,
2006-11-30 08:43:28 +03:00
__u8 ipproto ,
2006-11-03 03:39:14 +03:00
void ( * dscp_ecn_decapsulate ) ( struct ip6_tnl * t ,
struct ipv6hdr * ipv6h ,
struct sk_buff * skb ) )
2005-04-17 02:20:36 +04:00
{
struct ip6_tnl * t ;
2007-04-26 04:54:47 +04:00
struct ipv6hdr * ipv6h = ipv6_hdr ( skb ) ;
2005-04-17 02:20:36 +04:00
2007-02-09 18:30:33 +03:00
read_lock ( & ip6_tnl_lock ) ;
2005-04-17 02:20:36 +04:00
2008-04-16 12:22:43 +04:00
if ( ( t = ip6_tnl_lookup ( dev_net ( skb - > dev ) , & ipv6h - > saddr ,
2008-04-16 12:22:23 +04:00
& ipv6h - > daddr ) ) ! = NULL ) {
2006-11-30 08:43:28 +03:00
if ( t - > parms . proto ! = ipproto & & t - > parms . proto ! = 0 ) {
read_unlock ( & ip6_tnl_lock ) ;
goto discard ;
}
2005-04-17 02:20:36 +04:00
if ( ! xfrm6_policy_check ( NULL , XFRM_POLICY_IN , skb ) ) {
2007-02-09 18:30:33 +03:00
read_unlock ( & ip6_tnl_lock ) ;
2006-04-05 00:50:45 +04:00
goto discard ;
2005-04-17 02:20:36 +04:00
}
2006-11-25 04:06:27 +03:00
if ( ! ip6_tnl_rcv_ctl ( t ) ) {
2008-05-22 01:17:05 +04:00
t - > dev - > stats . rx_dropped + + ;
2007-02-09 18:30:33 +03:00
read_unlock ( & ip6_tnl_lock ) ;
2005-04-17 02:20:36 +04:00
goto discard ;
}
secpath_reset ( skb ) ;
2007-04-11 08:21:55 +04:00
skb - > mac_header = skb - > network_header ;
2007-04-11 07:45:18 +04:00
skb_reset_network_header ( skb ) ;
2006-11-03 03:39:14 +03:00
skb - > protocol = htons ( protocol ) ;
2005-04-17 02:20:36 +04:00
skb - > pkt_type = PACKET_HOST ;
memset ( skb - > cb , 0 , sizeof ( struct inet6_skb_parm ) ) ;
skb - > dev = t - > dev ;
dst_release ( skb - > dst ) ;
skb - > dst = NULL ;
2006-11-06 21:06:23 +03:00
nf_reset ( skb ) ;
2006-11-03 03:39:14 +03:00
dscp_ecn_decapsulate ( t , ipv6h , skb ) ;
2008-05-22 01:17:05 +04:00
t - > dev - > stats . rx_packets + + ;
t - > dev - > stats . rx_bytes + = skb - > len ;
2005-04-17 02:20:36 +04:00
netif_rx ( skb ) ;
2007-02-09 18:30:33 +03:00
read_unlock ( & ip6_tnl_lock ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2007-02-09 18:30:33 +03:00
read_unlock ( & ip6_tnl_lock ) ;
2005-04-17 02:20:36 +04:00
return 1 ;
2006-04-05 00:50:45 +04:00
discard :
kfree_skb ( skb ) ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
2007-02-14 18:43:16 +03:00
static int ip4ip6_rcv ( struct sk_buff * skb )
{
2006-11-30 08:43:28 +03:00
return ip6_tnl_rcv ( skb , ETH_P_IP , IPPROTO_IPIP ,
ip4ip6_dscp_ecn_decapsulate ) ;
2007-02-14 18:43:16 +03:00
}
2006-11-03 03:39:14 +03:00
static int ip6ip6_rcv ( struct sk_buff * skb )
{
2006-11-30 08:43:28 +03:00
return ip6_tnl_rcv ( skb , ETH_P_IPV6 , IPPROTO_IPV6 ,
ip6ip6_dscp_ecn_decapsulate ) ;
2006-11-03 03:39:14 +03:00
}
2006-11-25 04:08:32 +03:00
struct ipv6_tel_txoption {
struct ipv6_txoptions ops ;
__u8 dst_opt [ 8 ] ;
} ;
2005-04-17 02:20:36 +04:00
2006-11-25 04:08:32 +03:00
static void init_tel_txopt ( struct ipv6_tel_txoption * opt , __u8 encap_limit )
{
memset ( opt , 0 , sizeof ( struct ipv6_tel_txoption ) ) ;
2005-04-17 02:20:36 +04:00
2006-11-25 04:08:32 +03:00
opt - > dst_opt [ 2 ] = IPV6_TLV_TNL_ENCAP_LIMIT ;
opt - > dst_opt [ 3 ] = 1 ;
opt - > dst_opt [ 4 ] = encap_limit ;
opt - > dst_opt [ 5 ] = IPV6_TLV_PADN ;
opt - > dst_opt [ 6 ] = 1 ;
2005-04-17 02:20:36 +04:00
2006-11-25 04:08:32 +03:00
opt - > ops . dst0opt = ( struct ipv6_opt_hdr * ) opt - > dst_opt ;
opt - > ops . opt_nflen = 8 ;
2005-04-17 02:20:36 +04:00
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_addr_conflict - compare packet addresses to tunnel ' s own
2005-04-17 02:20:36 +04:00
* @ t : the outgoing tunnel device
2007-02-09 17:24:49 +03:00
* @ hdr : IPv6 header from the incoming packet
2005-04-17 02:20:36 +04:00
*
* Description :
2007-02-09 17:24:49 +03:00
* Avoid trivial tunneling loop by checking that tunnel exit - point
2005-04-17 02:20:36 +04:00
* doesn ' t match source of incoming packet .
*
2007-02-09 17:24:49 +03:00
* Return :
2005-04-17 02:20:36 +04:00
* 1 if conflict ,
* 0 else
* */
static inline int
2007-02-09 18:30:33 +03:00
ip6_tnl_addr_conflict ( struct ip6_tnl * t , struct ipv6hdr * hdr )
2005-04-17 02:20:36 +04:00
{
return ipv6_addr_equal ( & t - > parms . raddr , & hdr - > saddr ) ;
}
2006-11-25 04:06:27 +03:00
static inline int ip6_tnl_xmit_ctl ( struct ip6_tnl * t )
{
struct ip6_tnl_parm * p = & t - > parms ;
int ret = 0 ;
2008-04-16 12:23:44 +04:00
struct net * net = dev_net ( t - > dev ) ;
2006-11-25 04:06:27 +03:00
2007-02-09 17:24:49 +03:00
if ( p - > flags & IP6_TNL_F_CAP_XMIT ) {
2006-11-25 04:06:27 +03:00
struct net_device * ldev = NULL ;
if ( p - > link )
2008-04-16 12:23:44 +04:00
ldev = dev_get_by_index ( net , p - > link ) ;
2006-11-25 04:06:27 +03:00
2008-04-16 12:23:44 +04:00
if ( unlikely ( ! ipv6_chk_addr ( net , & p - > laddr , ldev , 0 ) ) )
2006-11-25 04:06:27 +03:00
printk ( KERN_WARNING
" %s xmit: Local address not yet configured! \n " ,
p - > name ) ;
else if ( ! ipv6_addr_is_multicast ( & p - > raddr ) & &
2008-04-16 12:23:44 +04:00
unlikely ( ipv6_chk_addr ( net , & p - > raddr , NULL , 0 ) ) )
2006-11-25 04:06:27 +03:00
printk ( KERN_WARNING
" %s xmit: Routing loop! "
" Remote address found on this node! \n " ,
p - > name ) ;
else
ret = 1 ;
if ( ldev )
dev_put ( ldev ) ;
}
return ret ;
}
2005-04-17 02:20:36 +04:00
/**
2006-11-05 16:56:45 +03:00
* ip6_tnl_xmit2 - encapsulate packet and send
2005-04-17 02:20:36 +04:00
* @ skb : the outgoing socket buffer
2007-02-09 17:24:49 +03:00
* @ dev : the outgoing tunnel device
2006-11-05 16:56:45 +03:00
* @ dsfield : dscp code for outer header
* @ fl : flow of tunneled packet
* @ encap_limit : encapsulation limit
* @ pmtu : Path MTU is stored if packet is too big
2005-04-17 02:20:36 +04:00
*
* Description :
* Build new header and do some sanity checks on the packet before sending
* it .
*
2007-02-09 17:24:49 +03:00
* Return :
2007-02-14 18:43:16 +03:00
* 0 on success
2006-11-05 16:56:45 +03:00
* - 1 fail
* % - EMSGSIZE message too big . return mtu in this case .
2005-04-17 02:20:36 +04:00
* */
2006-11-05 16:56:45 +03:00
static int ip6_tnl_xmit2 ( struct sk_buff * skb ,
struct net_device * dev ,
__u8 dsfield ,
struct flowi * fl ,
int encap_limit ,
__u32 * pmtu )
2005-04-17 02:20:36 +04:00
{
2006-01-09 09:05:26 +03:00
struct ip6_tnl * t = netdev_priv ( dev ) ;
2008-05-22 01:17:05 +04:00
struct net_device_stats * stats = & t - > dev - > stats ;
2007-04-26 04:54:47 +04:00
struct ipv6hdr * ipv6h = ipv6_hdr ( skb ) ;
2006-11-25 04:08:32 +03:00
struct ipv6_tel_txoption opt ;
2005-04-17 02:20:36 +04:00
struct dst_entry * dst ;
struct net_device * tdev ;
int mtu ;
2007-10-24 08:07:32 +04:00
unsigned int max_headroom = sizeof ( struct ipv6hdr ) ;
2005-04-17 02:20:36 +04:00
u8 proto ;
2006-11-05 16:56:45 +03:00
int err = - 1 ;
2005-04-17 02:20:36 +04:00
int pkt_len ;
if ( ( dst = ip6_tnl_dst_check ( t ) ) ! = NULL )
dst_hold ( dst ) ;
2005-09-09 01:27:47 +04:00
else {
2008-04-16 12:23:44 +04:00
dst = ip6_route_output ( dev_net ( dev ) , NULL , fl ) ;
2005-04-17 02:20:36 +04:00
2006-11-05 16:56:45 +03:00
if ( dst - > error | | xfrm_lookup ( & dst , fl , NULL , 0 ) < 0 )
2005-09-09 01:27:47 +04:00
goto tx_err_link_failure ;
}
2005-04-17 02:20:36 +04:00
tdev = dst - > dev ;
if ( tdev = = dev ) {
stats - > collisions + + ;
if ( net_ratelimit ( ) )
2007-02-09 17:24:49 +03:00
printk ( KERN_WARNING
2005-04-17 02:20:36 +04:00
" %s: Local routing loop detected! \n " ,
t - > parms . name ) ;
goto tx_err_dst_release ;
}
mtu = dst_mtu ( dst ) - sizeof ( * ipv6h ) ;
2006-11-25 04:08:32 +03:00
if ( encap_limit > = 0 ) {
2005-04-17 02:20:36 +04:00
max_headroom + = 8 ;
mtu - = 8 ;
}
if ( mtu < IPV6_MIN_MTU )
mtu = IPV6_MIN_MTU ;
2006-09-09 22:59:17 +04:00
if ( skb - > dst )
skb - > dst - > ops - > update_pmtu ( skb - > dst , mtu ) ;
2005-04-17 02:20:36 +04:00
if ( skb - > len > mtu ) {
2006-11-05 16:56:45 +03:00
* pmtu = mtu ;
err = - EMSGSIZE ;
2005-04-17 02:20:36 +04:00
goto tx_err_dst_release ;
}
/*
* Okay , now see if we can stuff it in the buffer as - is .
*/
max_headroom + = LL_RESERVED_SPACE ( tdev ) ;
2007-02-09 17:24:49 +03:00
2007-07-10 02:33:40 +04:00
if ( skb_headroom ( skb ) < max_headroom | | skb_shared ( skb ) | |
( skb_cloned ( skb ) & & ! skb_clone_writable ( skb , 0 ) ) ) {
2005-04-17 02:20:36 +04:00
struct sk_buff * new_skb ;
2007-02-09 17:24:49 +03:00
2005-04-17 02:20:36 +04:00
if ( ! ( new_skb = skb_realloc_headroom ( skb , max_headroom ) ) )
goto tx_err_dst_release ;
if ( skb - > sk )
skb_set_owner_w ( new_skb , skb - > sk ) ;
kfree_skb ( skb ) ;
skb = new_skb ;
}
dst_release ( skb - > dst ) ;
skb - > dst = dst_clone ( dst ) ;
2007-04-11 08:21:55 +04:00
skb - > transport_header = skb - > network_header ;
2005-04-17 02:20:36 +04:00
2006-11-05 16:56:45 +03:00
proto = fl - > proto ;
2006-11-25 04:08:32 +03:00
if ( encap_limit > = 0 ) {
init_tel_txopt ( & opt , encap_limit ) ;
ipv6_push_nfrag_opts ( skb , & opt . ops , & proto , NULL ) ;
}
2007-04-11 07:46:21 +04:00
skb_push ( skb , sizeof ( struct ipv6hdr ) ) ;
skb_reset_network_header ( skb ) ;
2007-04-26 04:54:47 +04:00
ipv6h = ipv6_hdr ( skb ) ;
2006-11-05 16:56:45 +03:00
* ( __be32 * ) ipv6h = fl - > fl6_flowlabel | htonl ( 0x60000000 ) ;
2005-04-17 02:20:36 +04:00
dsfield = INET_ECN_encapsulate ( 0 , dsfield ) ;
ipv6_change_dsfield ( ipv6h , ~ INET_ECN_MASK , dsfield ) ;
ipv6h - > hop_limit = t - > parms . hop_limit ;
ipv6h - > nexthdr = proto ;
2006-11-05 16:56:45 +03:00
ipv6_addr_copy ( & ipv6h - > saddr , & fl - > fl6_src ) ;
ipv6_addr_copy ( & ipv6h - > daddr , & fl - > fl6_dst ) ;
2005-04-17 02:20:36 +04:00
nf_reset ( skb ) ;
pkt_len = skb - > len ;
2008-01-12 06:15:08 +03:00
err = ip6_local_out ( skb ) ;
2005-04-17 02:20:36 +04:00
2006-11-14 16:21:36 +03:00
if ( net_xmit_eval ( err ) = = 0 ) {
2005-04-17 02:20:36 +04:00
stats - > tx_bytes + = pkt_len ;
stats - > tx_packets + + ;
} else {
stats - > tx_errors + + ;
stats - > tx_aborted_errors + + ;
}
ip6_tnl_dst_store ( t , dst ) ;
return 0 ;
tx_err_link_failure :
stats - > tx_carrier_errors + + ;
dst_link_failure ( skb ) ;
tx_err_dst_release :
dst_release ( dst ) ;
2006-11-05 16:56:45 +03:00
return err ;
}
2007-02-14 18:43:16 +03:00
static inline int
ip4ip6_tnl_xmit ( struct sk_buff * skb , struct net_device * dev )
{
struct ip6_tnl * t = netdev_priv ( dev ) ;
2007-04-21 09:47:35 +04:00
struct iphdr * iph = ip_hdr ( skb ) ;
2007-02-14 18:43:16 +03:00
int encap_limit = - 1 ;
struct flowi fl ;
__u8 dsfield ;
__u32 mtu ;
int err ;
2006-11-30 08:43:28 +03:00
if ( ( t - > parms . proto ! = IPPROTO_IPIP & & t - > parms . proto ! = 0 ) | |
! ip6_tnl_xmit_ctl ( t ) )
2007-02-14 18:43:16 +03:00
return - 1 ;
if ( ! ( t - > parms . flags & IP6_TNL_F_IGN_ENCAP_LIMIT ) )
encap_limit = t - > parms . encap_limit ;
memcpy ( & fl , & t - > fl , sizeof ( fl ) ) ;
fl . proto = IPPROTO_IPIP ;
dsfield = ipv4_get_dsfield ( iph ) ;
if ( ( t - > parms . flags & IP6_TNL_F_USE_ORIG_TCLASS ) )
2007-07-22 06:09:41 +04:00
fl . fl6_flowlabel | = htonl ( ( __u32 ) iph - > tos < < IPV6_TCLASS_SHIFT )
& IPV6_TCLASS_MASK ;
2007-02-14 18:43:16 +03:00
err = ip6_tnl_xmit2 ( skb , dev , dsfield , & fl , encap_limit , & mtu ) ;
if ( err ! = 0 ) {
/* XXX: send ICMP error even if DF is not set. */
if ( err = = - EMSGSIZE )
icmp_send ( skb , ICMP_DEST_UNREACH , ICMP_FRAG_NEEDED ,
htonl ( mtu ) ) ;
return - 1 ;
}
return 0 ;
}
2006-11-05 16:56:45 +03:00
static inline int
ip6ip6_tnl_xmit ( struct sk_buff * skb , struct net_device * dev )
{
struct ip6_tnl * t = netdev_priv ( dev ) ;
2007-04-26 04:54:47 +04:00
struct ipv6hdr * ipv6h = ipv6_hdr ( skb ) ;
2006-11-05 16:56:45 +03:00
int encap_limit = - 1 ;
__u16 offset ;
struct flowi fl ;
__u8 dsfield ;
__u32 mtu ;
int err ;
2006-11-30 08:43:28 +03:00
if ( ( t - > parms . proto ! = IPPROTO_IPV6 & & t - > parms . proto ! = 0 ) | |
! ip6_tnl_xmit_ctl ( t ) | | ip6_tnl_addr_conflict ( t , ipv6h ) )
2006-11-05 16:56:45 +03:00
return - 1 ;
2007-04-11 07:50:43 +04:00
offset = parse_tlv_tnl_enc_lim ( skb , skb_network_header ( skb ) ) ;
if ( offset > 0 ) {
2006-11-05 16:56:45 +03:00
struct ipv6_tlv_tnl_enc_lim * tel ;
2007-04-11 07:50:43 +04:00
tel = ( struct ipv6_tlv_tnl_enc_lim * ) & skb_network_header ( skb ) [ offset ] ;
2006-11-05 16:56:45 +03:00
if ( tel - > encap_limit = = 0 ) {
icmpv6_send ( skb , ICMPV6_PARAMPROB ,
ICMPV6_HDR_FIELD , offset + 2 , skb - > dev ) ;
return - 1 ;
}
encap_limit = tel - > encap_limit - 1 ;
} else if ( ! ( t - > parms . flags & IP6_TNL_F_IGN_ENCAP_LIMIT ) )
encap_limit = t - > parms . encap_limit ;
memcpy ( & fl , & t - > fl , sizeof ( fl ) ) ;
fl . proto = IPPROTO_IPV6 ;
dsfield = ipv6_get_dsfield ( ipv6h ) ;
if ( ( t - > parms . flags & IP6_TNL_F_USE_ORIG_TCLASS ) )
fl . fl6_flowlabel | = ( * ( __be32 * ) ipv6h & IPV6_TCLASS_MASK ) ;
if ( ( t - > parms . flags & IP6_TNL_F_USE_ORIG_FLOWLABEL ) )
fl . fl6_flowlabel | = ( * ( __be32 * ) ipv6h & IPV6_FLOWLABEL_MASK ) ;
err = ip6_tnl_xmit2 ( skb , dev , dsfield , & fl , encap_limit , & mtu ) ;
if ( err ! = 0 ) {
if ( err = = - EMSGSIZE )
icmpv6_send ( skb , ICMPV6_PKT_TOOBIG , 0 , mtu , dev ) ;
return - 1 ;
}
return 0 ;
}
static int
ip6_tnl_xmit ( struct sk_buff * skb , struct net_device * dev )
{
struct ip6_tnl * t = netdev_priv ( dev ) ;
2008-05-22 01:17:05 +04:00
struct net_device_stats * stats = & t - > dev - > stats ;
2006-11-05 16:56:45 +03:00
int ret ;
if ( t - > recursion + + ) {
2008-05-22 01:17:05 +04:00
stats - > collisions + + ;
2006-11-05 16:56:45 +03:00
goto tx_err ;
}
switch ( skb - > protocol ) {
2007-02-14 18:43:16 +03:00
case __constant_htons ( ETH_P_IP ) :
ret = ip4ip6_tnl_xmit ( skb , dev ) ;
break ;
2006-11-05 16:56:45 +03:00
case __constant_htons ( ETH_P_IPV6 ) :
ret = ip6ip6_tnl_xmit ( skb , dev ) ;
break ;
default :
goto tx_err ;
}
if ( ret < 0 )
goto tx_err ;
t - > recursion - - ;
return 0 ;
2005-04-17 02:20:36 +04:00
tx_err :
stats - > tx_errors + + ;
stats - > tx_dropped + + ;
kfree_skb ( skb ) ;
t - > recursion - - ;
return 0 ;
}
static void ip6_tnl_set_cap ( struct ip6_tnl * t )
{
struct ip6_tnl_parm * p = & t - > parms ;
2006-11-25 04:06:27 +03:00
int ltype = ipv6_addr_type ( & p - > laddr ) ;
int rtype = ipv6_addr_type ( & p - > raddr ) ;
2005-04-17 02:20:36 +04:00
p - > flags & = ~ ( IP6_TNL_F_CAP_XMIT | IP6_TNL_F_CAP_RCV ) ;
2006-11-25 04:06:27 +03:00
if ( ltype & ( IPV6_ADDR_UNICAST | IPV6_ADDR_MULTICAST ) & &
rtype & ( IPV6_ADDR_UNICAST | IPV6_ADDR_MULTICAST ) & &
! ( ( ltype | rtype ) & IPV6_ADDR_LOOPBACK ) & &
2006-11-25 04:06:53 +03:00
( ! ( ( ltype | rtype ) & IPV6_ADDR_LINKLOCAL ) | | p - > link ) ) {
2006-11-25 04:06:27 +03:00
if ( ltype & IPV6_ADDR_UNICAST )
p - > flags | = IP6_TNL_F_CAP_XMIT ;
if ( rtype & IPV6_ADDR_UNICAST )
p - > flags | = IP6_TNL_F_CAP_RCV ;
2005-04-17 02:20:36 +04:00
}
}
2007-02-09 18:30:33 +03:00
static void ip6_tnl_link_config ( struct ip6_tnl * t )
2005-04-17 02:20:36 +04:00
{
struct net_device * dev = t - > dev ;
struct ip6_tnl_parm * p = & t - > parms ;
struct flowi * fl = & t - > fl ;
memcpy ( & dev - > dev_addr , & p - > laddr , sizeof ( struct in6_addr ) ) ;
memcpy ( & dev - > broadcast , & p - > raddr , sizeof ( struct in6_addr ) ) ;
/* Set up flowi template */
ipv6_addr_copy ( & fl - > fl6_src , & p - > laddr ) ;
ipv6_addr_copy ( & fl - > fl6_dst , & p - > raddr ) ;
fl - > oif = p - > link ;
fl - > fl6_flowlabel = 0 ;
if ( ! ( p - > flags & IP6_TNL_F_USE_ORIG_TCLASS ) )
fl - > fl6_flowlabel | = IPV6_TCLASS_MASK & p - > flowinfo ;
if ( ! ( p - > flags & IP6_TNL_F_USE_ORIG_FLOWLABEL ) )
fl - > fl6_flowlabel | = IPV6_FLOWLABEL_MASK & p - > flowinfo ;
ip6_tnl_set_cap ( t ) ;
if ( p - > flags & IP6_TNL_F_CAP_XMIT & & p - > flags & IP6_TNL_F_CAP_RCV )
dev - > flags | = IFF_POINTOPOINT ;
else
dev - > flags & = ~ IFF_POINTOPOINT ;
dev - > iflink = p - > link ;
if ( p - > flags & IP6_TNL_F_CAP_XMIT ) {
2006-11-25 04:06:53 +03:00
int strict = ( ipv6_addr_type ( & p - > raddr ) &
( IPV6_ADDR_MULTICAST | IPV6_ADDR_LINKLOCAL ) ) ;
2008-04-16 12:23:44 +04:00
struct rt6_info * rt = rt6_lookup ( dev_net ( dev ) ,
& p - > raddr , & p - > laddr ,
2006-11-25 04:06:53 +03:00
p - > link , strict ) ;
2005-04-17 02:20:36 +04:00
if ( rt = = NULL )
return ;
if ( rt - > rt6i_dev ) {
dev - > hard_header_len = rt - > rt6i_dev - > hard_header_len +
sizeof ( struct ipv6hdr ) ;
dev - > mtu = rt - > rt6i_dev - > mtu - sizeof ( struct ipv6hdr ) ;
if ( dev - > mtu < IPV6_MIN_MTU )
dev - > mtu = IPV6_MIN_MTU ;
}
dst_release ( & rt - > u . dst ) ;
}
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_change - update the tunnel parameters
2005-04-17 02:20:36 +04:00
* @ t : tunnel to be changed
* @ p : tunnel configuration parameters
* @ active : ! = 0 if tunnel is ready for use
*
* Description :
2007-02-09 18:30:33 +03:00
* ip6_tnl_change ( ) updates the tunnel parameters
2005-04-17 02:20:36 +04:00
* */
static int
2007-02-09 18:30:33 +03:00
ip6_tnl_change ( struct ip6_tnl * t , struct ip6_tnl_parm * p )
2005-04-17 02:20:36 +04:00
{
ipv6_addr_copy ( & t - > parms . laddr , & p - > laddr ) ;
ipv6_addr_copy ( & t - > parms . raddr , & p - > raddr ) ;
t - > parms . flags = p - > flags ;
t - > parms . hop_limit = p - > hop_limit ;
t - > parms . encap_limit = p - > encap_limit ;
t - > parms . flowinfo = p - > flowinfo ;
2005-06-09 01:54:38 +04:00
t - > parms . link = p - > link ;
2006-11-30 08:43:28 +03:00
t - > parms . proto = p - > proto ;
2006-02-25 00:16:25 +03:00
ip6_tnl_dst_reset ( t ) ;
2007-02-09 18:30:33 +03:00
ip6_tnl_link_config ( t ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_ioctl - configure ipv6 tunnels from userspace
2005-04-17 02:20:36 +04:00
* @ dev : virtual device associated with tunnel
* @ ifr : parameters passed from userspace
* @ cmd : command to be performed
*
* Description :
2007-02-09 18:30:33 +03:00
* ip6_tnl_ioctl ( ) is used for managing IPv6 tunnels
2007-02-09 17:24:49 +03:00
* from userspace .
2005-04-17 02:20:36 +04:00
*
* The possible commands are the following :
* % SIOCGETTUNNEL : get tunnel parameters for device
* % SIOCADDTUNNEL : add tunnel matching given tunnel parameters
* % SIOCCHGTUNNEL : change tunnel parameters to those given
* % SIOCDELTUNNEL : delete tunnel
*
2007-02-09 17:24:49 +03:00
* The fallback device " ip6tnl0 " , created during module
2005-04-17 02:20:36 +04:00
* initialization , can be used for creating other tunnel devices .
*
* Return :
* 0 on success ,
* % - EFAULT if unable to copy data to or from userspace ,
* % - EPERM if current process hasn ' t % CAP_NET_ADMIN set
* % - EINVAL if passed tunnel parameters are invalid ,
* % - EEXIST if changing a tunnel ' s parameters would cause a conflict
* % - ENODEV if attempting to change or delete a nonexisting device
* */
static int
2007-02-09 18:30:33 +03:00
ip6_tnl_ioctl ( struct net_device * dev , struct ifreq * ifr , int cmd )
2005-04-17 02:20:36 +04:00
{
int err = 0 ;
struct ip6_tnl_parm p ;
struct ip6_tnl * t = NULL ;
2008-04-16 12:22:23 +04:00
struct net * net = dev_net ( dev ) ;
struct ip6_tnl_net * ip6n = net_generic ( net , ip6_tnl_net_id ) ;
2005-04-17 02:20:36 +04:00
switch ( cmd ) {
case SIOCGETTUNNEL :
2008-04-16 12:23:02 +04:00
if ( dev = = ip6n - > fb_tnl_dev ) {
2006-11-25 04:05:41 +03:00
if ( copy_from_user ( & p , ifr - > ifr_ifru . ifru_data , sizeof ( p ) ) ) {
2005-04-17 02:20:36 +04:00
err = - EFAULT ;
break ;
}
2008-04-16 12:22:23 +04:00
t = ip6_tnl_locate ( net , & p , 0 ) ;
2006-11-25 04:05:41 +03:00
}
if ( t = = NULL )
2006-01-09 09:05:26 +03:00
t = netdev_priv ( dev ) ;
2005-04-17 02:20:36 +04:00
memcpy ( & p , & t - > parms , sizeof ( p ) ) ;
if ( copy_to_user ( ifr - > ifr_ifru . ifru_data , & p , sizeof ( p ) ) ) {
err = - EFAULT ;
}
break ;
case SIOCADDTUNNEL :
case SIOCCHGTUNNEL :
err = - EPERM ;
if ( ! capable ( CAP_NET_ADMIN ) )
break ;
2006-11-25 04:05:41 +03:00
err = - EFAULT ;
if ( copy_from_user ( & p , ifr - > ifr_ifru . ifru_data , sizeof ( p ) ) )
2005-04-17 02:20:36 +04:00
break ;
2006-11-25 04:05:41 +03:00
err = - EINVAL ;
2006-11-30 08:43:28 +03:00
if ( p . proto ! = IPPROTO_IPV6 & & p . proto ! = IPPROTO_IPIP & &
p . proto ! = 0 )
2005-04-17 02:20:36 +04:00
break ;
2008-04-16 12:22:23 +04:00
t = ip6_tnl_locate ( net , & p , cmd = = SIOCADDTUNNEL ) ;
2008-04-16 12:23:02 +04:00
if ( dev ! = ip6n - > fb_tnl_dev & & cmd = = SIOCCHGTUNNEL ) {
2006-11-25 04:05:41 +03:00
if ( t ! = NULL ) {
if ( t - > dev ! = dev ) {
err = - EEXIST ;
break ;
}
} else
t = netdev_priv ( dev ) ;
2008-04-16 12:22:23 +04:00
ip6_tnl_unlink ( ip6n , t ) ;
2007-02-09 18:30:33 +03:00
err = ip6_tnl_change ( t , & p ) ;
2008-04-16 12:22:23 +04:00
ip6_tnl_link ( ip6n , t ) ;
2005-04-17 02:20:36 +04:00
netdev_state_change ( dev ) ;
}
2006-11-25 04:05:41 +03:00
if ( t ) {
2005-04-17 02:20:36 +04:00
err = 0 ;
2006-11-25 04:05:41 +03:00
if ( copy_to_user ( ifr - > ifr_ifru . ifru_data , & t - > parms , sizeof ( p ) ) )
err = - EFAULT ;
} else
err = ( cmd = = SIOCADDTUNNEL ? - ENOBUFS : - ENOENT ) ;
2005-04-17 02:20:36 +04:00
break ;
case SIOCDELTUNNEL :
err = - EPERM ;
if ( ! capable ( CAP_NET_ADMIN ) )
break ;
2008-04-16 12:23:02 +04:00
if ( dev = = ip6n - > fb_tnl_dev ) {
2006-11-25 04:05:41 +03:00
err = - EFAULT ;
if ( copy_from_user ( & p , ifr - > ifr_ifru . ifru_data , sizeof ( p ) ) )
2005-04-17 02:20:36 +04:00
break ;
2006-11-25 04:05:41 +03:00
err = - ENOENT ;
2008-04-16 12:22:23 +04:00
if ( ( t = ip6_tnl_locate ( net , & p , 0 ) ) = = NULL )
2005-04-17 02:20:36 +04:00
break ;
2006-11-25 04:05:41 +03:00
err = - EPERM ;
2008-04-16 12:23:02 +04:00
if ( t - > dev = = ip6n - > fb_tnl_dev )
2005-04-17 02:20:36 +04:00
break ;
2006-11-25 04:05:41 +03:00
dev = t - > dev ;
2005-04-17 02:20:36 +04:00
}
2007-02-07 11:09:58 +03:00
err = 0 ;
unregister_netdevice ( dev ) ;
2005-04-17 02:20:36 +04:00
break ;
default :
err = - EINVAL ;
}
return err ;
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_change_mtu - change mtu manually for tunnel device
2005-04-17 02:20:36 +04:00
* @ dev : virtual device associated with tunnel
* @ new_mtu : the new mtu
*
* Return :
* 0 on success ,
* % - EINVAL if mtu too small
* */
static int
2007-02-09 18:30:33 +03:00
ip6_tnl_change_mtu ( struct net_device * dev , int new_mtu )
2005-04-17 02:20:36 +04:00
{
if ( new_mtu < IPV6_MIN_MTU ) {
return - EINVAL ;
}
dev - > mtu = new_mtu ;
return 0 ;
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_dev_setup - setup virtual tunnel device
2005-04-17 02:20:36 +04:00
* @ dev : virtual device associated with tunnel
*
* Description :
* Initialize function pointers and device parameters
* */
2007-02-09 18:30:33 +03:00
static void ip6_tnl_dev_setup ( struct net_device * dev )
2005-04-17 02:20:36 +04:00
{
2007-02-09 18:30:33 +03:00
dev - > uninit = ip6_tnl_dev_uninit ;
2005-04-17 02:20:36 +04:00
dev - > destructor = free_netdev ;
2006-11-05 16:56:45 +03:00
dev - > hard_start_xmit = ip6_tnl_xmit ;
2007-02-09 18:30:33 +03:00
dev - > do_ioctl = ip6_tnl_ioctl ;
dev - > change_mtu = ip6_tnl_change_mtu ;
2005-04-17 02:20:36 +04:00
dev - > type = ARPHRD_TUNNEL6 ;
dev - > hard_header_len = LL_MAX_HEADER + sizeof ( struct ipv6hdr ) ;
dev - > mtu = ETH_DATA_LEN - sizeof ( struct ipv6hdr ) ;
dev - > flags | = IFF_NOARP ;
dev - > addr_len = sizeof ( struct in6_addr ) ;
2008-04-16 12:24:13 +04:00
dev - > features | = NETIF_F_NETNS_LOCAL ;
2005-04-17 02:20:36 +04:00
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_dev_init_gen - general initializer for all tunnel devices
2005-04-17 02:20:36 +04:00
* @ dev : virtual device associated with tunnel
* */
static inline void
2007-02-09 18:30:33 +03:00
ip6_tnl_dev_init_gen ( struct net_device * dev )
2005-04-17 02:20:36 +04:00
{
2006-01-09 09:05:26 +03:00
struct ip6_tnl * t = netdev_priv ( dev ) ;
2005-04-17 02:20:36 +04:00
t - > dev = dev ;
strcpy ( t - > parms . name , dev - > name ) ;
}
/**
2007-02-09 18:30:33 +03:00
* ip6_tnl_dev_init - initializer for all non fallback tunnel devices
2005-04-17 02:20:36 +04:00
* @ dev : virtual device associated with tunnel
* */
static int
2007-02-09 18:30:33 +03:00
ip6_tnl_dev_init ( struct net_device * dev )
2005-04-17 02:20:36 +04:00
{
2006-01-09 09:05:26 +03:00
struct ip6_tnl * t = netdev_priv ( dev ) ;
2007-02-09 18:30:33 +03:00
ip6_tnl_dev_init_gen ( dev ) ;
ip6_tnl_link_config ( t ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/**
2007-02-09 18:30:33 +03:00
* ip6_fb_tnl_dev_init - initializer for fallback tunnel device
2005-04-17 02:20:36 +04:00
* @ dev : fallback device
*
* Return : 0
* */
2007-02-09 17:24:49 +03:00
static int
2007-02-09 18:30:33 +03:00
ip6_fb_tnl_dev_init ( struct net_device * dev )
2005-04-17 02:20:36 +04:00
{
2006-01-09 09:05:26 +03:00
struct ip6_tnl * t = netdev_priv ( dev ) ;
2008-04-16 12:23:22 +04:00
struct net * net = dev_net ( dev ) ;
struct ip6_tnl_net * ip6n = net_generic ( net , ip6_tnl_net_id ) ;
2007-02-09 18:30:33 +03:00
ip6_tnl_dev_init_gen ( dev ) ;
2006-11-30 08:43:28 +03:00
t - > parms . proto = IPPROTO_IPV6 ;
2005-04-17 02:20:36 +04:00
dev_hold ( dev ) ;
2008-04-16 12:23:22 +04:00
ip6n - > tnls_wc [ 0 ] = t ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2007-02-14 18:43:16 +03:00
static struct xfrm6_tunnel ip4ip6_handler = {
. handler = ip4ip6_rcv ,
. err_handler = ip4ip6_err ,
. priority = 1 ,
} ;
2005-04-17 02:20:36 +04:00
static struct xfrm6_tunnel ip6ip6_handler = {
2005-07-20 01:03:34 +04:00
. handler = ip6ip6_rcv ,
. err_handler = ip6ip6_err ,
2006-03-28 13:12:13 +04:00
. priority = 1 ,
2005-04-17 02:20:36 +04:00
} ;
2008-04-16 12:23:22 +04:00
static void ip6_tnl_destroy_tunnels ( struct ip6_tnl_net * ip6n )
{
int h ;
struct ip6_tnl * t ;
for ( h = 0 ; h < HASH_SIZE ; h + + ) {
while ( ( t = ip6n - > tnls_r_l [ h ] ) ! = NULL )
unregister_netdevice ( t - > dev ) ;
}
t = ip6n - > tnls_wc [ 0 ] ;
unregister_netdevice ( t - > dev ) ;
}
2008-04-16 12:22:02 +04:00
static int ip6_tnl_init_net ( struct net * net )
{
int err ;
struct ip6_tnl_net * ip6n ;
err = - ENOMEM ;
2008-04-16 12:23:22 +04:00
ip6n = kzalloc ( sizeof ( struct ip6_tnl_net ) , GFP_KERNEL ) ;
2008-04-16 12:22:02 +04:00
if ( ip6n = = NULL )
goto err_alloc ;
err = net_assign_generic ( net , ip6_tnl_net_id , ip6n ) ;
if ( err < 0 )
goto err_assign ;
2008-04-16 12:23:22 +04:00
ip6n - > tnls [ 0 ] = ip6n - > tnls_wc ;
ip6n - > tnls [ 1 ] = ip6n - > tnls_r_l ;
2008-04-16 12:23:02 +04:00
err = - ENOMEM ;
ip6n - > fb_tnl_dev = alloc_netdev ( sizeof ( struct ip6_tnl ) , " ip6tnl0 " ,
ip6_tnl_dev_setup ) ;
if ( ! ip6n - > fb_tnl_dev )
goto err_alloc_dev ;
ip6n - > fb_tnl_dev - > init = ip6_fb_tnl_dev_init ;
dev_net_set ( ip6n - > fb_tnl_dev , net ) ;
err = register_netdev ( ip6n - > fb_tnl_dev ) ;
if ( err < 0 )
goto err_register ;
2008-04-16 12:22:02 +04:00
return 0 ;
2008-04-16 12:23:02 +04:00
err_register :
free_netdev ( ip6n - > fb_tnl_dev ) ;
err_alloc_dev :
/* nothing */
2008-04-16 12:22:02 +04:00
err_assign :
kfree ( ip6n ) ;
err_alloc :
return err ;
}
static void ip6_tnl_exit_net ( struct net * net )
{
struct ip6_tnl_net * ip6n ;
ip6n = net_generic ( net , ip6_tnl_net_id ) ;
2008-04-16 12:23:22 +04:00
rtnl_lock ( ) ;
ip6_tnl_destroy_tunnels ( ip6n ) ;
rtnl_unlock ( ) ;
2008-04-16 12:22:02 +04:00
kfree ( ip6n ) ;
}
static struct pernet_operations ip6_tnl_net_ops = {
. init = ip6_tnl_init_net ,
. exit = ip6_tnl_exit_net ,
} ;
2005-04-17 02:20:36 +04:00
/**
* ip6_tunnel_init - register protocol and reserve needed resources
*
* Return : 0 on success
* */
static int __init ip6_tunnel_init ( void )
{
int err ;
2007-02-14 18:43:16 +03:00
if ( xfrm6_tunnel_register ( & ip4ip6_handler , AF_INET ) ) {
2007-02-09 18:30:33 +03:00
printk ( KERN_ERR " ip6_tunnel init: can't register ip4ip6 \n " ) ;
2007-02-14 18:43:16 +03:00
err = - EAGAIN ;
goto out ;
}
2007-02-13 23:55:55 +03:00
if ( xfrm6_tunnel_register ( & ip6ip6_handler , AF_INET6 ) ) {
2007-02-09 18:30:33 +03:00
printk ( KERN_ERR " ip6_tunnel init: can't register ip6ip6 \n " ) ;
2007-02-14 18:43:16 +03:00
err = - EAGAIN ;
goto unreg_ip4ip6 ;
2005-04-17 02:20:36 +04:00
}
2008-04-16 12:22:02 +04:00
err = register_pernet_gen_device ( & ip6_tnl_net_id , & ip6_tnl_net_ops ) ;
if ( err < 0 )
goto err_pernet ;
2005-04-17 02:20:36 +04:00
return 0 ;
2008-04-16 12:22:02 +04:00
err_pernet :
2007-02-13 23:55:55 +03:00
xfrm6_tunnel_deregister ( & ip6ip6_handler , AF_INET6 ) ;
2007-02-14 18:43:16 +03:00
unreg_ip4ip6 :
xfrm6_tunnel_deregister ( & ip4ip6_handler , AF_INET ) ;
out :
2005-04-17 02:20:36 +04:00
return err ;
}
/**
* ip6_tunnel_cleanup - free resources and unregister protocol
* */
static void __exit ip6_tunnel_cleanup ( void )
{
2007-02-14 18:43:16 +03:00
if ( xfrm6_tunnel_deregister ( & ip4ip6_handler , AF_INET ) )
2007-02-09 18:30:33 +03:00
printk ( KERN_INFO " ip6_tunnel close: can't deregister ip4ip6 \n " ) ;
2007-02-14 18:43:16 +03:00
2007-02-13 23:55:55 +03:00
if ( xfrm6_tunnel_deregister ( & ip6ip6_handler , AF_INET6 ) )
2007-02-09 18:30:33 +03:00
printk ( KERN_INFO " ip6_tunnel close: can't deregister ip6ip6 \n " ) ;
2005-04-17 02:20:36 +04:00
2008-04-16 12:22:02 +04:00
unregister_pernet_gen_device ( ip6_tnl_net_id , & ip6_tnl_net_ops ) ;
2005-04-17 02:20:36 +04:00
}
module_init ( ip6_tunnel_init ) ;
module_exit ( ip6_tunnel_cleanup ) ;