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>
# 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>
2006-09-22 14:15:41 -07:00
int sysctl_ip6frag_high_thresh __read_mostly = 256 * 1024 ;
int sysctl_ip6frag_low_thresh __read_mostly = 192 * 1024 ;
2005-04-16 15:20:36 -07:00
2006-09-22 14:15:41 -07:00
int sysctl_ip6frag_time __read_mostly = IPV6_FRAG_TIMEOUT ;
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
{
2005-11-16 12:55:37 -08:00
struct hlist_node list ;
2005-04-16 15:20:36 -07:00
struct list_head lru_list ; /* lru list member */
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 ;
spinlock_t lock ;
atomic_t refcnt ;
struct timer_list timer ; /* expire timer */
struct sk_buff * fragments ;
int len ;
int meat ;
int iif ;
2007-04-19 16:16:32 -07:00
ktime_t stamp ;
2005-04-16 15:20:36 -07:00
unsigned int csum ;
__u8 last_in ; /* has first/last segment arrived? */
# define COMPLETE 4
# define FIRST_IN 2
# define LAST_IN 1
__u16 nhoffset ;
} ;
/* Hash table. */
# define IP6Q_HASHSZ 64
2005-11-16 12:55:37 -08:00
static struct hlist_head ip6_frag_hash [ IP6Q_HASHSZ ] ;
2005-04-16 15:20:36 -07:00
static DEFINE_RWLOCK ( ip6_frag_lock ) ;
static u32 ip6_frag_hash_rnd ;
static LIST_HEAD ( ip6_frag_lru_list ) ;
int ip6_frag_nqueues = 0 ;
static __inline__ void __fq_unlink ( struct frag_queue * fq )
{
2005-11-16 12:55:37 -08:00
hlist_del ( & fq - > list ) ;
2005-04-16 15:20:36 -07:00
list_del ( & fq - > lru_list ) ;
ip6_frag_nqueues - - ;
}
static __inline__ void fq_unlink ( struct frag_queue * fq )
{
write_lock ( & ip6_frag_lock ) ;
__fq_unlink ( fq ) ;
write_unlock ( & ip6_frag_lock ) ;
}
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 ;
c + = ip6_frag_hash_rnd ;
__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 ) ;
return c & ( IP6Q_HASHSZ - 1 ) ;
}
static struct timer_list ip6_frag_secret_timer ;
2006-09-22 14:15:41 -07:00
int sysctl_ip6frag_secret_interval __read_mostly = 10 * 60 * HZ ;
2005-04-16 15:20:36 -07:00
static void ip6_frag_secret_rebuild ( unsigned long dummy )
{
unsigned long now = jiffies ;
int i ;
write_lock ( & ip6_frag_lock ) ;
get_random_bytes ( & ip6_frag_hash_rnd , sizeof ( u32 ) ) ;
for ( i = 0 ; i < IP6Q_HASHSZ ; i + + ) {
struct frag_queue * q ;
2005-11-16 12:55:37 -08:00
struct hlist_node * p , * n ;
2005-04-16 15:20:36 -07:00
2005-11-16 12:55:37 -08:00
hlist_for_each_entry_safe ( q , p , n , & ip6_frag_hash [ i ] , list ) {
2005-04-16 15:20:36 -07:00
unsigned int hval = ip6qhashfn ( q - > id ,
& q - > saddr ,
& q - > daddr ) ;
if ( hval ! = i ) {
2005-11-16 12:55:37 -08:00
hlist_del ( & q - > list ) ;
2005-04-16 15:20:36 -07:00
/* Relink to new hash chain. */
2005-11-16 12:55:37 -08:00
hlist_add_head ( & q - > list ,
& ip6_frag_hash [ hval ] ) ;
2005-04-16 15:20:36 -07:00
2005-11-16 12:55:37 -08:00
}
2005-04-16 15:20:36 -07:00
}
}
write_unlock ( & ip6_frag_lock ) ;
mod_timer ( & ip6_frag_secret_timer , now + sysctl_ip6frag_secret_interval ) ;
}
atomic_t ip6_frag_mem = ATOMIC_INIT ( 0 ) ;
/* Memory Tracking Functions. */
static inline void frag_kfree_skb ( struct sk_buff * skb , int * work )
{
if ( work )
* work - = skb - > truesize ;
atomic_sub ( skb - > truesize , & ip6_frag_mem ) ;
kfree_skb ( skb ) ;
}
static inline void frag_free_queue ( struct frag_queue * fq , int * work )
{
if ( work )
* work - = sizeof ( struct frag_queue ) ;
atomic_sub ( sizeof ( struct frag_queue ) , & ip6_frag_mem ) ;
kfree ( fq ) ;
}
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 ;
atomic_add ( sizeof ( struct frag_queue ) , & ip6_frag_mem ) ;
return fq ;
}
/* Destruction primitives. */
/* Complete destruction of fq. */
static void ip6_frag_destroy ( struct frag_queue * fq , int * work )
{
struct sk_buff * fp ;
BUG_TRAP ( fq - > last_in & COMPLETE ) ;
BUG_TRAP ( del_timer ( & fq - > timer ) = = 0 ) ;
/* Release all fragment data. */
fp = fq - > fragments ;
while ( fp ) {
struct sk_buff * xp = fp - > next ;
frag_kfree_skb ( fp , work ) ;
fp = xp ;
}
frag_free_queue ( fq , work ) ;
}
static __inline__ void fq_put ( struct frag_queue * fq , int * work )
{
if ( atomic_dec_and_test ( & fq - > refcnt ) )
ip6_frag_destroy ( fq , work ) ;
}
/* 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 )
{
if ( del_timer ( & fq - > timer ) )
atomic_dec ( & fq - > refcnt ) ;
if ( ! ( fq - > last_in & COMPLETE ) ) {
fq_unlink ( fq ) ;
atomic_dec ( & fq - > refcnt ) ;
fq - > last_in | = COMPLETE ;
}
}
2006-11-04 20:11:37 +09:00
static void ip6_evictor ( struct inet6_dev * idev )
2005-04-16 15:20:36 -07:00
{
struct frag_queue * fq ;
struct list_head * tmp ;
int work ;
work = atomic_read ( & ip6_frag_mem ) - sysctl_ip6frag_low_thresh ;
if ( work < = 0 )
return ;
while ( work > 0 ) {
read_lock ( & ip6_frag_lock ) ;
if ( list_empty ( & ip6_frag_lru_list ) ) {
read_unlock ( & ip6_frag_lock ) ;
return ;
}
tmp = ip6_frag_lru_list . next ;
fq = list_entry ( tmp , struct frag_queue , lru_list ) ;
atomic_inc ( & fq - > refcnt ) ;
read_unlock ( & ip6_frag_lock ) ;
spin_lock ( & fq - > lock ) ;
if ( ! ( fq - > last_in & COMPLETE ) )
fq_kill ( fq ) ;
spin_unlock ( & fq - > lock ) ;
fq_put ( fq , & work ) ;
2006-11-04 20:11:37 +09:00
IP6_INC_STATS_BH ( idev , IPSTATS_MIB_REASMFAILS ) ;
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
spin_lock ( & fq - > lock ) ;
if ( fq - > last_in & COMPLETE )
goto out ;
fq_kill ( fq ) ;
2006-11-04 20:11:37 +09:00
dev = dev_get_by_index ( fq - > iif ) ;
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. */
if ( ! ( fq - > last_in & FIRST_IN ) | | ! fq - > fragments )
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 .
*/
fq - > fragments - > dev = dev ;
icmpv6_send ( fq - > 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 ) ;
2005-04-16 15:20:36 -07:00
spin_unlock ( & fq - > lock ) ;
fq_put ( fq , NULL ) ;
}
/* Creation primitives. */
2006-04-10 16:05:34 -07:00
static struct frag_queue * ip6_frag_intern ( struct frag_queue * fq_in )
2005-04-16 15:20:36 -07:00
{
struct frag_queue * fq ;
2006-04-10 16:05:34 -07:00
unsigned int hash ;
2005-11-16 12:55:37 -08:00
# ifdef CONFIG_SMP
struct hlist_node * n ;
# endif
2005-04-16 15:20:36 -07:00
write_lock ( & ip6_frag_lock ) ;
2006-04-10 16:05:34 -07:00
hash = ip6qhashfn ( fq_in - > id , & fq_in - > saddr , & fq_in - > daddr ) ;
2005-04-16 15:20:36 -07:00
# ifdef CONFIG_SMP
2005-11-16 12:55:37 -08:00
hlist_for_each_entry ( fq , n , & ip6_frag_hash [ hash ] , list ) {
2007-02-09 23:24:49 +09:00
if ( fq - > id = = fq_in - > id & &
2005-04-16 15:20:36 -07:00
ipv6_addr_equal ( & fq_in - > saddr , & fq - > saddr ) & &
ipv6_addr_equal ( & fq_in - > daddr , & fq - > daddr ) ) {
atomic_inc ( & fq - > refcnt ) ;
write_unlock ( & ip6_frag_lock ) ;
fq_in - > last_in | = COMPLETE ;
fq_put ( fq_in , NULL ) ;
return fq ;
}
}
# endif
fq = fq_in ;
if ( ! mod_timer ( & fq - > timer , jiffies + sysctl_ip6frag_time ) )
atomic_inc ( & fq - > refcnt ) ;
atomic_inc ( & fq - > refcnt ) ;
2005-11-16 12:55:37 -08:00
hlist_add_head ( & fq - > list , & ip6_frag_hash [ hash ] ) ;
2005-04-16 15:20:36 -07:00
INIT_LIST_HEAD ( & fq - > lru_list ) ;
list_add_tail ( & fq - > lru_list , & ip6_frag_lru_list ) ;
ip6_frag_nqueues + + ;
write_unlock ( & ip6_frag_lock ) ;
return fq ;
}
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 ,
2006-11-04 20:11:37 +09:00
struct inet6_dev * idev )
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 ) ;
init_timer ( & fq - > timer ) ;
fq - > timer . function = ip6_frag_expire ;
fq - > timer . data = ( long ) fq ;
spin_lock_init ( & fq - > lock ) ;
atomic_set ( & fq - > refcnt , 1 ) ;
2006-04-10 16:05:34 -07:00
return ip6_frag_intern ( fq ) ;
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
read_lock ( & ip6_frag_lock ) ;
2006-04-10 16:05:34 -07:00
hash = ip6qhashfn ( id , src , dst ) ;
2005-11-16 12:55:37 -08:00
hlist_for_each_entry ( fq , n , & ip6_frag_hash [ hash ] , 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 ) ) {
atomic_inc ( & fq - > refcnt ) ;
read_unlock ( & ip6_frag_lock ) ;
return fq ;
}
}
read_unlock ( & ip6_frag_lock ) ;
2006-11-04 20:11:37 +09:00
return ip6_frag_create ( id , src , dst , idev ) ;
2005-04-16 15:20:36 -07:00
}
2007-02-09 23:24:49 +09:00
static void 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 ;
int offset , end ;
if ( fq - > last_in & COMPLETE )
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-02-09 23:24:49 +09:00
return ;
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 .
*/
if ( end < fq - > len | |
( ( fq - > last_in & LAST_IN ) & & end ! = fq - > len ) )
goto err ;
fq - > last_in | = LAST_IN ;
fq - > len = end ;
} 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 ) ) ;
return ;
}
if ( end > fq - > len ) {
/* Some bits beyond end -> corruption. */
if ( fq - > last_in & LAST_IN )
goto err ;
fq - > len = end ;
}
}
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 ;
for ( next = fq - > fragments ; next ! = NULL ; next = next - > next ) {
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 */
fq - > meat - = i ;
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
fq - > fragments = next ;
fq - > meat - = free_it - > len ;
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
fq - > fragments = skb ;
if ( skb - > dev )
fq - > iif = skb - > dev - > ifindex ;
skb - > dev = NULL ;
2007-04-19 16:16:32 -07:00
fq - > stamp = skb - > tstamp ;
2005-04-16 15:20:36 -07:00
fq - > meat + = skb - > len ;
atomic_add ( skb - > truesize , & ip6_frag_mem ) ;
/* The first fragment.
* nhoffset is obtained from the first fragment , of course .
*/
if ( offset = = 0 ) {
fq - > nhoffset = nhoff ;
fq - > last_in | = FIRST_IN ;
}
write_lock ( & ip6_frag_lock ) ;
list_move_tail ( & fq - > lru_list , & ip6_frag_lru_list ) ;
write_unlock ( & ip6_frag_lock ) ;
return ;
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 ) ;
}
/*
* 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 .
*/
static int ip6_frag_reasm ( struct frag_queue * fq , struct sk_buff * * skb_in ,
struct net_device * dev )
{
struct sk_buff * fp , * head = fq - > fragments ;
int payload_len ;
unsigned int nhoff ;
fq_kill ( fq ) ;
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 ) ) -
sizeof ( struct ipv6hdr ) + fq - > len -
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 ;
atomic_add ( clone - > truesize , & ip6_frag_mem ) ;
}
/* 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 ) ) ;
2005-04-16 15:20:36 -07:00
atomic_sub ( head - > truesize , & ip6_frag_mem ) ;
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 ;
atomic_sub ( fp - > truesize , & ip6_frag_mem ) ;
}
head - > next = NULL ;
head - > dev = dev ;
2007-04-19 16:16:32 -07:00
head - > tstamp = fq - > 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
* skb_in = head ;
/* 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 ( ) ;
2005-04-16 15:20:36 -07:00
fq - > fragments = NULL ;
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 ;
}
2006-01-06 23:02:34 -08:00
static int ipv6_frag_rcv ( struct sk_buff * * skbp )
2005-04-16 15:20:36 -07:00
{
2007-02-09 23:24:49 +09:00
struct sk_buff * skb = * skbp ;
2005-04-16 15:20:36 -07:00
struct net_device * dev = skb - > dev ;
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 ;
}
if ( atomic_read ( & ip6_frag_mem ) > sysctl_ip6frag_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 ) {
2005-04-16 15:20:36 -07:00
int ret = - 1 ;
spin_lock ( & fq - > lock ) ;
2006-01-06 23:02:34 -08:00
ip6_frag_queue ( fq , skb , fhdr , IP6CB ( skb ) - > nhoff ) ;
2005-04-16 15:20:36 -07:00
if ( fq - > last_in = = ( FIRST_IN | LAST_IN ) & &
fq - > meat = = fq - > len )
2006-01-06 23:02:34 -08:00
ret = ip6_frag_reasm ( fq , skbp , dev ) ;
2005-04-16 15:20:36 -07:00
spin_unlock ( & fq - > lock ) ;
fq_put ( fq , NULL ) ;
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 " ) ;
ip6_frag_hash_rnd = ( u32 ) ( ( num_physpages ^ ( num_physpages > > 7 ) ) ^
( jiffies ^ ( jiffies > > 6 ) ) ) ;
init_timer ( & ip6_frag_secret_timer ) ;
ip6_frag_secret_timer . function = ip6_frag_secret_rebuild ;
ip6_frag_secret_timer . expires = jiffies + sysctl_ip6frag_secret_interval ;
add_timer ( & ip6_frag_secret_timer ) ;
}