2005-04-16 15:20:36 -07:00
/*
* IPv6 fragment reassembly
2007-02-09 23:24:49 +09:00
* Linux INET6 implementation
2005-04-16 15:20:36 -07:00
*
* Authors :
2007-02-09 23:24:49 +09:00
* Pedro Roque < roque @ di . fc . ul . pt >
2005-04-16 15:20:36 -07:00
*
* $ Id : reassembly . c , v 1.26 2001 / 03 / 07 22 : 00 : 57 davem Exp $
*
* Based on : net / ipv4 / ip_fragment . c
*
* 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 23:24:49 +09:00
/*
* Fixes :
2005-04-16 15:20:36 -07:00
* Andi Kleen Make it work with multiple hosts .
* More RFC compliance .
*
* Horst von Brand Add missing # include < linux / string . h >
* Alexey Kuznetsov SMP races , threading , cleanup .
* Patrick McHardy LRU queue of frag heads for evictor .
* Mitsuru KANDA @ USAGI Register inet6_protocol { } .
* David Stevens and
* YOSHIFUJI , H . @ USAGI Always remove fragment header to
* calculate ICV correctly .
*/
# include <linux/errno.h>
# include <linux/types.h>
# include <linux/string.h>
# include <linux/socket.h>
# include <linux/sockios.h>
# include <linux/jiffies.h>
# include <linux/net.h>
# include <linux/list.h>
# include <linux/netdevice.h>
# include <linux/in6.h>
# include <linux/ipv6.h>
# include <linux/icmpv6.h>
# include <linux/random.h>
# include <linux/jhash.h>
2007-10-15 01:28:47 -07:00
# include <linux/skbuff.h>
2005-04-16 15:20:36 -07:00
# include <net/sock.h>
# include <net/snmp.h>
# include <net/ipv6.h>
2006-11-04 20:11:37 +09:00
# include <net/ip6_route.h>
2005-04-16 15:20:36 -07:00
# include <net/protocol.h>
# include <net/transp_v6.h>
# include <net/rawv6.h>
# include <net/ndisc.h>
# include <net/addrconf.h>
2007-10-15 02:24:19 -07:00
# include <net/inet_frag.h>
2005-04-16 15:20:36 -07:00
struct ip6frag_skb_cb
{
struct inet6_skb_parm h ;
int offset ;
} ;
# define FRAG6_CB(skb) ((struct ip6frag_skb_cb*)((skb)->cb))
/*
* Equivalent of ipv4 struct ipq
*/
struct frag_queue
{
2007-10-15 02:24:19 -07:00
struct inet_frag_queue q ;
2005-04-16 15:20:36 -07:00
2006-11-14 20:56:00 -08:00
__be32 id ; /* fragment id */
2005-04-16 15:20:36 -07:00
struct in6_addr saddr ;
struct in6_addr daddr ;
int iif ;
unsigned int csum ;
__u16 nhoffset ;
} ;
2007-10-15 02:33:45 -07:00
struct inet_frags_ctl ip6_frags_ctl __read_mostly = {
. high_thresh = 256 * 1024 ,
. low_thresh = 192 * 1024 ,
. timeout = IPV6_FRAG_TIMEOUT ,
. secret_interval = 10 * 60 * HZ ,
} ;
2007-10-15 02:31:52 -07:00
static struct inet_frags ip6_frags ;
2005-04-16 15:20:36 -07:00
2007-10-15 02:31:52 -07:00
int ip6_frag_nqueues ( void )
{
return ip6_frags . nqueues ;
}
2005-04-16 15:20:36 -07:00
2007-10-15 02:31:52 -07:00
int ip6_frag_mem ( void )
{
return atomic_read ( & ip6_frags . mem ) ;
}
2005-04-16 15:20:36 -07:00
2007-10-15 01:28:47 -07:00
static int ip6_frag_reasm ( struct frag_queue * fq , struct sk_buff * prev ,
struct net_device * dev ) ;
2006-04-10 16:05:34 -07:00
/*
* callers should be careful not to use the hash value outside the ipfrag_lock
* as doing so could race with ipfrag_hash_rnd being recalculated .
*/
2006-11-14 20:56:00 -08:00
static unsigned int ip6qhashfn ( __be32 id , struct in6_addr * saddr ,
2005-04-16 15:20:36 -07:00
struct in6_addr * daddr )
{
u32 a , b , c ;
2006-11-14 20:56:00 -08:00
a = ( __force u32 ) saddr - > s6_addr32 [ 0 ] ;
b = ( __force u32 ) saddr - > s6_addr32 [ 1 ] ;
c = ( __force u32 ) saddr - > s6_addr32 [ 2 ] ;
2005-04-16 15:20:36 -07:00
a + = JHASH_GOLDEN_RATIO ;
b + = JHASH_GOLDEN_RATIO ;
2007-10-15 02:31:52 -07:00
c + = ip6_frags . rnd ;
2005-04-16 15:20:36 -07:00
__jhash_mix ( a , b , c ) ;
2006-11-14 20:56:00 -08:00
a + = ( __force u32 ) saddr - > s6_addr32 [ 3 ] ;
b + = ( __force u32 ) daddr - > s6_addr32 [ 0 ] ;
c + = ( __force u32 ) daddr - > s6_addr32 [ 1 ] ;
2005-04-16 15:20:36 -07:00
__jhash_mix ( a , b , c ) ;
2006-11-14 20:56:00 -08:00
a + = ( __force u32 ) daddr - > s6_addr32 [ 2 ] ;
b + = ( __force u32 ) daddr - > s6_addr32 [ 3 ] ;
c + = ( __force u32 ) id ;
2005-04-16 15:20:36 -07:00
__jhash_mix ( a , b , c ) ;
2007-10-15 02:31:52 -07:00
return c & ( INETFRAGS_HASHSZ - 1 ) ;
2005-04-16 15:20:36 -07:00
}
2007-10-15 02:38:08 -07:00
static unsigned int ip6_hashfn ( struct inet_frag_queue * q )
2005-04-16 15:20:36 -07:00
{
2007-10-15 02:38:08 -07:00
struct frag_queue * fq ;
2005-04-16 15:20:36 -07:00
2007-10-15 02:38:08 -07:00
fq = container_of ( q , struct frag_queue , q ) ;
return ip6qhashfn ( fq - > id , & fq - > saddr , & fq - > daddr ) ;
2005-04-16 15:20:36 -07:00
}
2007-10-17 19:44:34 -07:00
int ip6_frag_equal ( struct inet_frag_queue * q1 , struct inet_frag_queue * q2 )
{
struct frag_queue * fq1 , * fq2 ;
fq1 = container_of ( q1 , struct frag_queue , q ) ;
fq2 = container_of ( q2 , struct frag_queue , q ) ;
return ( fq1 - > id = = fq2 - > id & &
ipv6_addr_equal ( & fq2 - > saddr , & fq1 - > saddr ) & &
ipv6_addr_equal ( & fq2 - > daddr , & fq1 - > daddr ) ) ;
}
EXPORT_SYMBOL ( ip6_frag_equal ) ;
2005-04-16 15:20:36 -07:00
/* Memory Tracking Functions. */
static inline void frag_kfree_skb ( struct sk_buff * skb , int * work )
{
if ( work )
* work - = skb - > truesize ;
2007-10-15 02:31:52 -07:00
atomic_sub ( skb - > truesize , & ip6_frags . mem ) ;
2005-04-16 15:20:36 -07:00
kfree_skb ( skb ) ;
}
2007-10-15 02:39:14 -07:00
static void ip6_frag_free ( struct inet_frag_queue * fq )
2005-04-16 15:20:36 -07:00
{
2007-10-15 02:39:14 -07:00
kfree ( container_of ( fq , struct frag_queue , q ) ) ;
2005-04-16 15:20:36 -07:00
}
static inline struct frag_queue * frag_alloc_queue ( void )
{
2006-03-20 23:01:17 -08:00
struct frag_queue * fq = kzalloc ( sizeof ( struct frag_queue ) , GFP_ATOMIC ) ;
2005-04-16 15:20:36 -07:00
if ( ! fq )
return NULL ;
2007-10-15 02:31:52 -07:00
atomic_add ( sizeof ( struct frag_queue ) , & ip6_frags . mem ) ;
2005-04-16 15:20:36 -07:00
return fq ;
}
/* Destruction primitives. */
2007-10-15 02:41:09 -07:00
static __inline__ void fq_put ( struct frag_queue * fq )
2005-04-16 15:20:36 -07:00
{
2007-10-15 02:41:56 -07:00
inet_frag_put ( & fq - > q , & ip6_frags ) ;
2005-04-16 15:20:36 -07:00
}
/* Kill fq entry. It is not destroyed immediately,
* because caller ( and someone more ) holds reference count .
*/
static __inline__ void fq_kill ( struct frag_queue * fq )
{
2007-10-15 02:37:18 -07:00
inet_frag_kill ( & fq - > q , & ip6_frags ) ;
2005-04-16 15:20:36 -07:00
}
2006-11-04 20:11:37 +09:00
static void ip6_evictor ( struct inet6_dev * idev )
2005-04-16 15:20:36 -07:00
{
2007-10-15 02:40:06 -07:00
int evicted ;
evicted = inet_frag_evictor ( & ip6_frags ) ;
if ( evicted )
IP6_ADD_STATS_BH ( idev , IPSTATS_MIB_REASMFAILS , evicted ) ;
2005-04-16 15:20:36 -07:00
}
static void ip6_frag_expire ( unsigned long data )
{
struct frag_queue * fq = ( struct frag_queue * ) data ;
2006-11-04 20:11:37 +09:00
struct net_device * dev = NULL ;
2005-04-16 15:20:36 -07:00
2007-10-15 02:24:19 -07:00
spin_lock ( & fq - > q . lock ) ;
2005-04-16 15:20:36 -07:00
2007-10-15 02:24:19 -07:00
if ( fq - > q . last_in & COMPLETE )
2005-04-16 15:20:36 -07:00
goto out ;
fq_kill ( fq ) ;
2007-09-17 11:56:21 -07:00
dev = dev_get_by_index ( & init_net , fq - > iif ) ;
2006-11-04 20:11:37 +09:00
if ( ! dev )
goto out ;
rcu_read_lock ( ) ;
IP6_INC_STATS_BH ( __in6_dev_get ( dev ) , IPSTATS_MIB_REASMTIMEOUT ) ;
IP6_INC_STATS_BH ( __in6_dev_get ( dev ) , IPSTATS_MIB_REASMFAILS ) ;
rcu_read_unlock ( ) ;
2005-04-16 15:20:36 -07:00
2006-03-20 23:01:17 -08:00
/* Don't send error if the first segment did not arrive. */
2007-10-15 02:24:19 -07:00
if ( ! ( fq - > q . last_in & FIRST_IN ) | | ! fq - > q . fragments )
2006-03-20 23:01:17 -08:00
goto out ;
/*
But use as source device on which LAST ARRIVED
segment was received . And do not use fq - > dev
pointer directly , device might already disappeared .
*/
2007-10-15 02:24:19 -07:00
fq - > q . fragments - > dev = dev ;
icmpv6_send ( fq - > q . fragments , ICMPV6_TIME_EXCEED , ICMPV6_EXC_FRAGTIME , 0 , dev ) ;
2005-04-16 15:20:36 -07:00
out :
2006-11-04 20:11:37 +09:00
if ( dev )
dev_put ( dev ) ;
2007-10-15 02:24:19 -07:00
spin_unlock ( & fq - > q . lock ) ;
2007-10-15 02:41:09 -07:00
fq_put ( fq ) ;
2005-04-16 15:20:36 -07:00
}
/* Creation primitives. */
2007-10-17 19:43:37 -07:00
static struct frag_queue * ip6_frag_intern ( struct frag_queue * fq_in ,
unsigned int hash )
2005-04-16 15:20:36 -07:00
{
2007-10-17 19:44:34 -07:00
struct inet_frag_queue * q ;
2005-04-16 15:20:36 -07:00
2007-10-17 19:44:34 -07:00
q = inet_frag_intern ( & fq_in - > q , & ip6_frags , hash ) ;
return container_of ( q , struct frag_queue , q ) ;
2005-04-16 15:20:36 -07:00
}
static struct frag_queue *
2006-11-14 20:56:00 -08:00
ip6_frag_create ( __be32 id , struct in6_addr * src , struct in6_addr * dst ,
2007-10-17 19:43:37 -07:00
struct inet6_dev * idev , unsigned int hash )
2005-04-16 15:20:36 -07:00
{
struct frag_queue * fq ;
if ( ( fq = frag_alloc_queue ( ) ) = = NULL )
goto oom ;
fq - > id = id ;
ipv6_addr_copy ( & fq - > saddr , src ) ;
ipv6_addr_copy ( & fq - > daddr , dst ) ;
2007-10-15 02:24:19 -07:00
init_timer ( & fq - > q . timer ) ;
fq - > q . timer . function = ip6_frag_expire ;
fq - > q . timer . data = ( long ) fq ;
spin_lock_init ( & fq - > q . lock ) ;
atomic_set ( & fq - > q . refcnt , 1 ) ;
2005-04-16 15:20:36 -07:00
2007-10-17 19:43:37 -07:00
return ip6_frag_intern ( fq , hash ) ;
2005-04-16 15:20:36 -07:00
oom :
2006-11-04 20:11:37 +09:00
IP6_INC_STATS_BH ( idev , IPSTATS_MIB_REASMFAILS ) ;
2005-04-16 15:20:36 -07:00
return NULL ;
}
static __inline__ struct frag_queue *
2006-11-14 20:56:00 -08:00
fq_find ( __be32 id , struct in6_addr * src , struct in6_addr * dst ,
2006-11-04 20:11:37 +09:00
struct inet6_dev * idev )
2005-04-16 15:20:36 -07:00
{
struct frag_queue * fq ;
2005-11-16 12:55:37 -08:00
struct hlist_node * n ;
2006-04-10 16:05:34 -07:00
unsigned int hash ;
2005-04-16 15:20:36 -07:00
2007-10-15 02:31:52 -07:00
read_lock ( & ip6_frags . lock ) ;
2006-04-10 16:05:34 -07:00
hash = ip6qhashfn ( id , src , dst ) ;
2007-10-15 02:31:52 -07:00
hlist_for_each_entry ( fq , n , & ip6_frags . hash [ hash ] , q . list ) {
2007-02-09 23:24:49 +09:00
if ( fq - > id = = id & &
2005-04-16 15:20:36 -07:00
ipv6_addr_equal ( src , & fq - > saddr ) & &
ipv6_addr_equal ( dst , & fq - > daddr ) ) {
2007-10-15 02:24:19 -07:00
atomic_inc ( & fq - > q . refcnt ) ;
2007-10-15 02:31:52 -07:00
read_unlock ( & ip6_frags . lock ) ;
2005-04-16 15:20:36 -07:00
return fq ;
}
}
2007-10-15 02:31:52 -07:00
read_unlock ( & ip6_frags . lock ) ;
2005-04-16 15:20:36 -07:00
2007-10-17 19:43:37 -07:00
return ip6_frag_create ( id , src , dst , idev , hash ) ;
2005-04-16 15:20:36 -07:00
}
2007-10-15 01:28:47 -07:00
static int ip6_frag_queue ( struct frag_queue * fq , struct sk_buff * skb ,
2005-04-16 15:20:36 -07:00
struct frag_hdr * fhdr , int nhoff )
{
struct sk_buff * prev , * next ;
2007-10-15 01:28:47 -07:00
struct net_device * dev ;
2005-04-16 15:20:36 -07:00
int offset , end ;
2007-10-15 02:24:19 -07:00
if ( fq - > q . last_in & COMPLETE )
2005-04-16 15:20:36 -07:00
goto err ;
offset = ntohs ( fhdr - > frag_off ) & ~ 0x7 ;
2007-04-25 17:54:47 -07:00
end = offset + ( ntohs ( ipv6_hdr ( skb ) - > payload_len ) -
( ( u8 * ) ( fhdr + 1 ) - ( u8 * ) ( ipv6_hdr ( skb ) + 1 ) ) ) ;
2005-04-16 15:20:36 -07:00
if ( ( unsigned int ) end > IPV6_MAXPLEN ) {
2006-11-04 20:11:37 +09:00
IP6_INC_STATS_BH ( ip6_dst_idev ( skb - > dst ) ,
IPSTATS_MIB_INHDRERRORS ) ;
2007-04-10 20:50:43 -07:00
icmpv6_param_prob ( skb , ICMPV6_HDR_FIELD ,
( ( u8 * ) & fhdr - > frag_off -
skb_network_header ( skb ) ) ) ;
2007-10-15 01:28:47 -07:00
return - 1 ;
2005-04-16 15:20:36 -07:00
}
2007-04-10 20:50:43 -07:00
if ( skb - > ip_summed = = CHECKSUM_COMPLETE ) {
const unsigned char * nh = skb_network_header ( skb ) ;
2007-02-09 23:24:49 +09:00
skb - > csum = csum_sub ( skb - > csum ,
2007-04-10 20:50:43 -07:00
csum_partial ( nh , ( u8 * ) ( fhdr + 1 ) - nh ,
0 ) ) ;
}
2005-04-16 15:20:36 -07:00
/* Is this the final fragment? */
if ( ! ( fhdr - > frag_off & htons ( IP6_MF ) ) ) {
/* If we already have some bits beyond end
* or have different end , the segment is corrupted .
*/
2007-10-15 02:24:19 -07:00
if ( end < fq - > q . len | |
( ( fq - > q . last_in & LAST_IN ) & & end ! = fq - > q . len ) )
2005-04-16 15:20:36 -07:00
goto err ;
2007-10-15 02:24:19 -07:00
fq - > q . last_in | = LAST_IN ;
fq - > q . len = end ;
2005-04-16 15:20:36 -07:00
} else {
/* Check if the fragment is rounded to 8 bytes.
* Required by the RFC .
*/
if ( end & 0x7 ) {
/* RFC2460 says always send parameter problem in
* this case . - DaveM
*/
2006-11-04 20:11:37 +09:00
IP6_INC_STATS_BH ( ip6_dst_idev ( skb - > dst ) ,
IPSTATS_MIB_INHDRERRORS ) ;
2007-02-09 23:24:49 +09:00
icmpv6_param_prob ( skb , ICMPV6_HDR_FIELD ,
2005-04-16 15:20:36 -07:00
offsetof ( struct ipv6hdr , payload_len ) ) ;
2007-10-15 01:28:47 -07:00
return - 1 ;
2005-04-16 15:20:36 -07:00
}
2007-10-15 02:24:19 -07:00
if ( end > fq - > q . len ) {
2005-04-16 15:20:36 -07:00
/* Some bits beyond end -> corruption. */
2007-10-15 02:24:19 -07:00
if ( fq - > q . last_in & LAST_IN )
2005-04-16 15:20:36 -07:00
goto err ;
2007-10-15 02:24:19 -07:00
fq - > q . len = end ;
2005-04-16 15:20:36 -07:00
}
}
if ( end = = offset )
goto err ;
/* Point into the IP datagram 'data' part. */
if ( ! pskb_pull ( skb , ( u8 * ) ( fhdr + 1 ) - skb - > data ) )
goto err ;
2007-02-09 23:24:49 +09:00
2005-09-08 12:57:43 -07:00
if ( pskb_trim_rcsum ( skb , end - offset ) )
goto err ;
2005-04-16 15:20:36 -07:00
/* Find out which fragments are in front and at the back of us
* in the chain of fragments so far . We must know where to put
* this fragment , right ?
*/
prev = NULL ;
2007-10-15 02:24:19 -07:00
for ( next = fq - > q . fragments ; next ! = NULL ; next = next - > next ) {
2005-04-16 15:20:36 -07:00
if ( FRAG6_CB ( next ) - > offset > = offset )
break ; /* bingo! */
prev = next ;
}
/* We found where to put this one. Check for overlap with
* preceding fragment , and , if needed , align things so that
* any overlaps are eliminated .
*/
if ( prev ) {
int i = ( FRAG6_CB ( prev ) - > offset + prev - > len ) - offset ;
if ( i > 0 ) {
offset + = i ;
if ( end < = offset )
goto err ;
if ( ! pskb_pull ( skb , i ) )
goto err ;
if ( skb - > ip_summed ! = CHECKSUM_UNNECESSARY )
skb - > ip_summed = CHECKSUM_NONE ;
}
}
/* Look for overlap with succeeding segments.
* If we can merge fragments , do it .
*/
while ( next & & FRAG6_CB ( next ) - > offset < end ) {
int i = end - FRAG6_CB ( next ) - > offset ; /* overlap is 'i' bytes */
if ( i < next - > len ) {
/* Eat head of the next overlapped fragment
* and leave the loop . The next ones cannot overlap .
*/
if ( ! pskb_pull ( next , i ) )
goto err ;
FRAG6_CB ( next ) - > offset + = i ; /* next fragment */
2007-10-15 02:24:19 -07:00
fq - > q . meat - = i ;
2005-04-16 15:20:36 -07:00
if ( next - > ip_summed ! = CHECKSUM_UNNECESSARY )
next - > ip_summed = CHECKSUM_NONE ;
break ;
} else {
struct sk_buff * free_it = next ;
/* Old fragment is completely overridden with
* new one drop it .
*/
next = next - > next ;
if ( prev )
prev - > next = next ;
else
2007-10-15 02:24:19 -07:00
fq - > q . fragments = next ;
2005-04-16 15:20:36 -07:00
2007-10-15 02:24:19 -07:00
fq - > q . meat - = free_it - > len ;
2005-04-16 15:20:36 -07:00
frag_kfree_skb ( free_it , NULL ) ;
}
}
FRAG6_CB ( skb ) - > offset = offset ;
/* Insert this fragment in the chain of fragments. */
skb - > next = next ;
if ( prev )
prev - > next = skb ;
else
2007-10-15 02:24:19 -07:00
fq - > q . fragments = skb ;
2005-04-16 15:20:36 -07:00
2007-10-15 01:28:47 -07:00
dev = skb - > dev ;
if ( dev ) {
fq - > iif = dev - > ifindex ;
skb - > dev = NULL ;
}
2007-10-15 02:24:19 -07:00
fq - > q . stamp = skb - > tstamp ;
fq - > q . meat + = skb - > len ;
2007-10-15 02:31:52 -07:00
atomic_add ( skb - > truesize , & ip6_frags . mem ) ;
2005-04-16 15:20:36 -07:00
/* The first fragment.
* nhoffset is obtained from the first fragment , of course .
*/
if ( offset = = 0 ) {
fq - > nhoffset = nhoff ;
2007-10-15 02:24:19 -07:00
fq - > q . last_in | = FIRST_IN ;
2005-04-16 15:20:36 -07:00
}
2007-10-15 01:28:47 -07:00
2007-10-15 02:24:19 -07:00
if ( fq - > q . last_in = = ( FIRST_IN | LAST_IN ) & & fq - > q . meat = = fq - > q . len )
2007-10-15 01:28:47 -07:00
return ip6_frag_reasm ( fq , prev , dev ) ;
2007-10-15 02:31:52 -07:00
write_lock ( & ip6_frags . lock ) ;
list_move_tail ( & fq - > q . lru_list , & ip6_frags . lru_list ) ;
write_unlock ( & ip6_frags . lock ) ;
2007-10-15 01:28:47 -07:00
return - 1 ;
2005-04-16 15:20:36 -07:00
err :
2006-11-04 20:11:37 +09:00
IP6_INC_STATS ( ip6_dst_idev ( skb - > dst ) , IPSTATS_MIB_REASMFAILS ) ;
2005-04-16 15:20:36 -07:00
kfree_skb ( skb ) ;
2007-10-15 01:28:47 -07:00
return - 1 ;
2005-04-16 15:20:36 -07:00
}
/*
* Check if this packet is complete .
* Returns NULL on failure by any reason , and pointer
* to current nexthdr field in reassembled frame .
*
* It is called with locked fq , and caller must check that
* queue is eligible for reassembly i . e . it is not COMPLETE ,
* the last and the first frames arrived and all the bits are here .
*/
2007-10-15 01:28:47 -07:00
static int ip6_frag_reasm ( struct frag_queue * fq , struct sk_buff * prev ,
2005-04-16 15:20:36 -07:00
struct net_device * dev )
{
2007-10-15 02:24:19 -07:00
struct sk_buff * fp , * head = fq - > q . fragments ;
2005-04-16 15:20:36 -07:00
int payload_len ;
unsigned int nhoff ;
fq_kill ( fq ) ;
2007-10-15 01:28:47 -07:00
/* Make the one we just received the head. */
if ( prev ) {
head = prev - > next ;
fp = skb_clone ( head , GFP_ATOMIC ) ;
if ( ! fp )
goto out_oom ;
fp - > next = head - > next ;
prev - > next = fp ;
2007-10-15 02:24:19 -07:00
skb_morph ( head , fq - > q . fragments ) ;
head - > next = fq - > q . fragments - > next ;
2007-10-15 01:28:47 -07:00
2007-10-15 02:24:19 -07:00
kfree_skb ( fq - > q . fragments ) ;
fq - > q . fragments = head ;
2007-10-15 01:28:47 -07:00
}
2005-04-16 15:20:36 -07:00
BUG_TRAP ( head ! = NULL ) ;
BUG_TRAP ( FRAG6_CB ( head ) - > offset = = 0 ) ;
/* Unfragmented part is taken from the first segment. */
2007-04-10 20:50:43 -07:00
payload_len = ( ( head - > data - skb_network_header ( head ) ) -
2007-10-15 02:24:19 -07:00
sizeof ( struct ipv6hdr ) + fq - > q . len -
2007-04-10 20:50:43 -07:00
sizeof ( struct frag_hdr ) ) ;
2005-04-16 15:20:36 -07:00
if ( payload_len > IPV6_MAXPLEN )
goto out_oversize ;
/* Head of list must not be cloned. */
if ( skb_cloned ( head ) & & pskb_expand_head ( head , 0 , 0 , GFP_ATOMIC ) )
goto out_oom ;
/* If the first fragment is fragmented itself, we split
* it to two chunks : the first with data and paged part
* and the second , holding only fragments . */
if ( skb_shinfo ( head ) - > frag_list ) {
struct sk_buff * clone ;
int i , plen = 0 ;
if ( ( clone = alloc_skb ( 0 , GFP_ATOMIC ) ) = = NULL )
goto out_oom ;
clone - > next = head - > next ;
head - > next = clone ;
skb_shinfo ( clone ) - > frag_list = skb_shinfo ( head ) - > frag_list ;
skb_shinfo ( head ) - > frag_list = NULL ;
for ( i = 0 ; i < skb_shinfo ( head ) - > nr_frags ; i + + )
plen + = skb_shinfo ( head ) - > frags [ i ] . size ;
clone - > len = clone - > data_len = head - > data_len - plen ;
head - > data_len - = clone - > len ;
head - > len - = clone - > len ;
clone - > csum = 0 ;
clone - > ip_summed = head - > ip_summed ;
2007-10-15 02:31:52 -07:00
atomic_add ( clone - > truesize , & ip6_frags . mem ) ;
2005-04-16 15:20:36 -07:00
}
/* We have to remove fragment header from datagram and to relocate
* header in order to calculate ICV correctly . */
nhoff = fq - > nhoffset ;
2007-04-10 21:21:55 -07:00
skb_network_header ( head ) [ nhoff ] = skb_transport_header ( head ) [ 0 ] ;
2007-02-09 23:24:49 +09:00
memmove ( head - > head + sizeof ( struct frag_hdr ) , head - > head ,
2005-04-16 15:20:36 -07:00
( head - > data - head - > head ) - sizeof ( struct frag_hdr ) ) ;
2007-04-10 21:21:55 -07:00
head - > mac_header + = sizeof ( struct frag_hdr ) ;
head - > network_header + = sizeof ( struct frag_hdr ) ;
2005-04-16 15:20:36 -07:00
skb_shinfo ( head ) - > frag_list = head - > next ;
2007-03-13 13:06:52 -03:00
skb_reset_transport_header ( head ) ;
2007-04-10 20:50:43 -07:00
skb_push ( head , head - > data - skb_network_header ( head ) ) ;
2007-10-15 02:31:52 -07:00
atomic_sub ( head - > truesize , & ip6_frags . mem ) ;
2005-04-16 15:20:36 -07:00
for ( fp = head - > next ; fp ; fp = fp - > next ) {
head - > data_len + = fp - > len ;
head - > len + = fp - > len ;
if ( head - > ip_summed ! = fp - > ip_summed )
head - > ip_summed = CHECKSUM_NONE ;
2006-08-29 16:44:56 -07:00
else if ( head - > ip_summed = = CHECKSUM_COMPLETE )
2005-04-16 15:20:36 -07:00
head - > csum = csum_add ( head - > csum , fp - > csum ) ;
head - > truesize + = fp - > truesize ;
2007-10-15 02:31:52 -07:00
atomic_sub ( fp - > truesize , & ip6_frags . mem ) ;
2005-04-16 15:20:36 -07:00
}
head - > next = NULL ;
head - > dev = dev ;
2007-10-15 02:24:19 -07:00
head - > tstamp = fq - > q . stamp ;
2007-04-25 17:54:47 -07:00
ipv6_hdr ( head ) - > payload_len = htons ( payload_len ) ;
2006-01-06 23:02:34 -08:00
IP6CB ( head ) - > nhoff = nhoff ;
2005-04-16 15:20:36 -07:00
/* Yes, and fold redundant checksum back. 8) */
2006-08-29 16:44:56 -07:00
if ( head - > ip_summed = = CHECKSUM_COMPLETE )
2007-04-10 20:50:43 -07:00
head - > csum = csum_partial ( skb_network_header ( head ) ,
2007-03-16 17:26:39 -03:00
skb_network_header_len ( head ) ,
2007-04-10 20:50:43 -07:00
head - > csum ) ;
2005-04-16 15:20:36 -07:00
2006-11-04 20:11:37 +09:00
rcu_read_lock ( ) ;
IP6_INC_STATS_BH ( __in6_dev_get ( dev ) , IPSTATS_MIB_REASMOKS ) ;
rcu_read_unlock ( ) ;
2007-10-15 02:24:19 -07:00
fq - > q . fragments = NULL ;
2005-04-16 15:20:36 -07:00
return 1 ;
out_oversize :
if ( net_ratelimit ( ) )
printk ( KERN_DEBUG " ip6_frag_reasm: payload len = %d \n " , payload_len ) ;
goto out_fail ;
out_oom :
if ( net_ratelimit ( ) )
printk ( KERN_DEBUG " ip6_frag_reasm: no memory for reassembly \n " ) ;
out_fail :
2006-11-04 20:11:37 +09:00
rcu_read_lock ( ) ;
IP6_INC_STATS_BH ( __in6_dev_get ( dev ) , IPSTATS_MIB_REASMFAILS ) ;
rcu_read_unlock ( ) ;
2005-04-16 15:20:36 -07:00
return - 1 ;
}
2007-10-15 12:50:28 -07:00
static int ipv6_frag_rcv ( struct sk_buff * skb )
2005-04-16 15:20:36 -07:00
{
struct frag_hdr * fhdr ;
struct frag_queue * fq ;
2007-04-25 17:54:47 -07:00
struct ipv6hdr * hdr = ipv6_hdr ( skb ) ;
2005-04-16 15:20:36 -07:00
2006-11-04 20:11:37 +09:00
IP6_INC_STATS_BH ( ip6_dst_idev ( skb - > dst ) , IPSTATS_MIB_REASMREQDS ) ;
2005-04-16 15:20:36 -07:00
/* Jumbo payload inhibits frag. header */
if ( hdr - > payload_len = = 0 ) {
2006-11-04 20:11:37 +09:00
IP6_INC_STATS ( ip6_dst_idev ( skb - > dst ) , IPSTATS_MIB_INHDRERRORS ) ;
2007-03-16 17:26:39 -03:00
icmpv6_param_prob ( skb , ICMPV6_HDR_FIELD ,
skb_network_header_len ( skb ) ) ;
2005-04-16 15:20:36 -07:00
return - 1 ;
}
2007-04-25 17:55:53 -07:00
if ( ! pskb_may_pull ( skb , ( skb_transport_offset ( skb ) +
sizeof ( struct frag_hdr ) ) ) ) {
2006-11-04 20:11:37 +09:00
IP6_INC_STATS ( ip6_dst_idev ( skb - > dst ) , IPSTATS_MIB_INHDRERRORS ) ;
2007-03-16 17:26:39 -03:00
icmpv6_param_prob ( skb , ICMPV6_HDR_FIELD ,
skb_network_header_len ( skb ) ) ;
2005-04-16 15:20:36 -07:00
return - 1 ;
}
2007-04-25 17:54:47 -07:00
hdr = ipv6_hdr ( skb ) ;
2007-04-25 18:04:18 -07:00
fhdr = ( struct frag_hdr * ) skb_transport_header ( skb ) ;
2005-04-16 15:20:36 -07:00
if ( ! ( fhdr - > frag_off & htons ( 0xFFF9 ) ) ) {
/* It is not a fragmented frame */
2007-04-10 21:21:55 -07:00
skb - > transport_header + = sizeof ( struct frag_hdr ) ;
2006-11-04 20:11:37 +09:00
IP6_INC_STATS_BH ( ip6_dst_idev ( skb - > dst ) , IPSTATS_MIB_REASMOKS ) ;
2005-04-16 15:20:36 -07:00
2007-04-10 20:50:43 -07:00
IP6CB ( skb ) - > nhoff = ( u8 * ) fhdr - skb_network_header ( skb ) ;
2005-04-16 15:20:36 -07:00
return 1 ;
}
2007-10-15 02:33:45 -07:00
if ( atomic_read ( & ip6_frags . mem ) > ip6_frags_ctl . high_thresh )
2006-11-04 20:11:37 +09:00
ip6_evictor ( ip6_dst_idev ( skb - > dst ) ) ;
2005-04-16 15:20:36 -07:00
2006-11-04 20:11:37 +09:00
if ( ( fq = fq_find ( fhdr - > identification , & hdr - > saddr , & hdr - > daddr ,
ip6_dst_idev ( skb - > dst ) ) ) ! = NULL ) {
2007-10-15 01:28:47 -07:00
int ret ;
2005-04-16 15:20:36 -07:00
2007-10-15 02:24:19 -07:00
spin_lock ( & fq - > q . lock ) ;
2005-04-16 15:20:36 -07:00
2007-10-15 01:28:47 -07:00
ret = ip6_frag_queue ( fq , skb , fhdr , IP6CB ( skb ) - > nhoff ) ;
2005-04-16 15:20:36 -07:00
2007-10-15 02:24:19 -07:00
spin_unlock ( & fq - > q . lock ) ;
2007-10-15 02:41:09 -07:00
fq_put ( fq ) ;
2005-04-16 15:20:36 -07:00
return ret ;
}
2006-11-04 20:11:37 +09:00
IP6_INC_STATS_BH ( ip6_dst_idev ( skb - > dst ) , IPSTATS_MIB_REASMFAILS ) ;
2005-04-16 15:20:36 -07:00
kfree_skb ( skb ) ;
return - 1 ;
}
static struct inet6_protocol frag_protocol =
{
. handler = ipv6_frag_rcv ,
. flags = INET6_PROTO_NOPOLICY ,
} ;
void __init ipv6_frag_init ( void )
{
if ( inet6_add_protocol ( & frag_protocol , IPPROTO_FRAGMENT ) < 0 )
printk ( KERN_ERR " ipv6_frag_init: Could not register protocol \n " ) ;
2007-10-15 02:33:45 -07:00
ip6_frags . ctl = & ip6_frags_ctl ;
2007-10-15 02:38:08 -07:00
ip6_frags . hashfn = ip6_hashfn ;
2007-10-15 02:39:14 -07:00
ip6_frags . destructor = ip6_frag_free ;
ip6_frags . skb_free = NULL ;
ip6_frags . qsize = sizeof ( struct frag_queue ) ;
2007-10-17 19:44:34 -07:00
ip6_frags . equal = ip6_frag_equal ;
2007-10-15 02:31:52 -07:00
inet_frags_init ( & ip6_frags ) ;
2005-04-16 15:20:36 -07:00
}