2007-10-15 13:31:52 +04:00
/*
* inet fragments management
*
* 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 .
*
* Authors : Pavel Emelyanov < xemul @ openvz . org >
* Started as consolidation of ipv4 / ip_fragment . c ,
* ipv6 / reassembly . and ipv6 nf conntrack reassembly
*/
# include <linux/list.h>
# include <linux/spinlock.h>
# include <linux/module.h>
# include <linux/timer.h>
# include <linux/mm.h>
2007-10-15 13:38:08 +04:00
# include <linux/random.h>
2007-10-15 13:39:14 +04:00
# include <linux/skbuff.h>
# include <linux/rtnetlink.h>
2007-10-15 13:31:52 +04:00
# include <net/inet_frag.h>
2007-10-15 13:38:08 +04:00
static void inet_frag_secret_rebuild ( unsigned long dummy )
{
struct inet_frags * f = ( struct inet_frags * ) dummy ;
unsigned long now = jiffies ;
int i ;
write_lock ( & f - > lock ) ;
get_random_bytes ( & f - > rnd , sizeof ( u32 ) ) ;
for ( i = 0 ; i < INETFRAGS_HASHSZ ; i + + ) {
struct inet_frag_queue * q ;
struct hlist_node * p , * n ;
hlist_for_each_entry_safe ( q , p , n , & f - > hash [ i ] , list ) {
unsigned int hval = f - > hashfn ( q ) ;
if ( hval ! = i ) {
hlist_del ( & q - > list ) ;
/* Relink to new hash chain. */
hlist_add_head ( & q - > list , & f - > hash [ hval ] ) ;
}
}
}
write_unlock ( & f - > lock ) ;
2008-01-22 17:11:04 +03:00
mod_timer ( & f - > secret_timer , now + f - > secret_interval ) ;
2007-10-15 13:38:08 +04:00
}
2007-10-15 13:31:52 +04:00
void inet_frags_init ( struct inet_frags * f )
{
int i ;
for ( i = 0 ; i < INETFRAGS_HASHSZ ; i + + )
INIT_HLIST_HEAD ( & f - > hash [ i ] ) ;
rwlock_init ( & f - > lock ) ;
f - > rnd = ( u32 ) ( ( num_physpages ^ ( num_physpages > > 7 ) ) ^
( jiffies ^ ( jiffies > > 6 ) ) ) ;
2008-01-24 08:20:07 +03:00
setup_timer ( & f - > secret_timer , inet_frag_secret_rebuild ,
( unsigned long ) f ) ;
2008-01-22 17:11:04 +03:00
f - > secret_timer . expires = jiffies + f - > secret_interval ;
2007-10-15 13:38:08 +04:00
add_timer ( & f - > secret_timer ) ;
2007-10-15 13:31:52 +04:00
}
EXPORT_SYMBOL ( inet_frags_init ) ;
2008-01-22 17:06:23 +03:00
void inet_frags_init_net ( struct netns_frags * nf )
{
nf - > nqueues = 0 ;
2008-01-22 17:07:25 +03:00
atomic_set ( & nf - > mem , 0 ) ;
2008-01-22 17:11:48 +03:00
INIT_LIST_HEAD ( & nf - > lru_list ) ;
2008-01-22 17:06:23 +03:00
}
EXPORT_SYMBOL ( inet_frags_init_net ) ;
2007-10-15 13:31:52 +04:00
void inet_frags_fini ( struct inet_frags * f )
{
2007-10-15 13:38:08 +04:00
del_timer ( & f - > secret_timer ) ;
2007-10-15 13:31:52 +04:00
}
EXPORT_SYMBOL ( inet_frags_fini ) ;
2007-10-15 13:37:18 +04:00
2008-01-22 17:12:39 +03:00
void inet_frags_exit_net ( struct netns_frags * nf , struct inet_frags * f )
{
nf - > low_thresh = 0 ;
2008-03-29 03:30:18 +03:00
local_bh_disable ( ) ;
2008-01-22 17:12:39 +03:00
inet_frag_evictor ( nf , f ) ;
2008-03-29 03:30:18 +03:00
local_bh_enable ( ) ;
2008-01-22 17:12:39 +03:00
}
EXPORT_SYMBOL ( inet_frags_exit_net ) ;
2007-10-15 13:37:18 +04:00
static inline void fq_unlink ( struct inet_frag_queue * fq , struct inet_frags * f )
{
write_lock ( & f - > lock ) ;
hlist_del ( & fq - > list ) ;
list_del ( & fq - > lru_list ) ;
2008-01-22 17:06:23 +03:00
fq - > net - > nqueues - - ;
2007-10-15 13:37:18 +04:00
write_unlock ( & f - > lock ) ;
}
void inet_frag_kill ( struct inet_frag_queue * fq , struct inet_frags * f )
{
if ( del_timer ( & fq - > timer ) )
atomic_dec ( & fq - > refcnt ) ;
2008-03-29 02:35:27 +03:00
if ( ! ( fq - > last_in & INET_FRAG_COMPLETE ) ) {
2007-10-15 13:37:18 +04:00
fq_unlink ( fq , f ) ;
atomic_dec ( & fq - > refcnt ) ;
2008-03-29 02:35:27 +03:00
fq - > last_in | = INET_FRAG_COMPLETE ;
2007-10-15 13:37:18 +04:00
}
}
EXPORT_SYMBOL ( inet_frag_kill ) ;
2007-10-15 13:39:14 +04:00
2008-01-22 17:07:25 +03:00
static inline void frag_kfree_skb ( struct netns_frags * nf , struct inet_frags * f ,
struct sk_buff * skb , int * work )
2007-10-15 13:39:14 +04:00
{
if ( work )
* work - = skb - > truesize ;
2008-01-22 17:07:25 +03:00
atomic_sub ( skb - > truesize , & nf - > mem ) ;
2007-10-15 13:39:14 +04:00
if ( f - > skb_free )
f - > skb_free ( skb ) ;
kfree_skb ( skb ) ;
}
void inet_frag_destroy ( struct inet_frag_queue * q , struct inet_frags * f ,
int * work )
{
struct sk_buff * fp ;
2008-01-22 17:07:25 +03:00
struct netns_frags * nf ;
2007-10-15 13:39:14 +04:00
2008-07-26 08:43:18 +04:00
WARN_ON ( ! ( q - > last_in & INET_FRAG_COMPLETE ) ) ;
WARN_ON ( del_timer ( & q - > timer ) ! = 0 ) ;
2007-10-15 13:39:14 +04:00
/* Release all fragment data. */
fp = q - > fragments ;
2008-01-22 17:07:25 +03:00
nf = q - > net ;
2007-10-15 13:39:14 +04:00
while ( fp ) {
struct sk_buff * xp = fp - > next ;
2008-01-22 17:07:25 +03:00
frag_kfree_skb ( nf , f , fp , work ) ;
2007-10-15 13:39:14 +04:00
fp = xp ;
}
if ( work )
* work - = f - > qsize ;
2008-01-22 17:07:25 +03:00
atomic_sub ( f - > qsize , & nf - > mem ) ;
2007-10-15 13:39:14 +04:00
2007-10-18 06:48:26 +04:00
if ( f - > destructor )
f - > destructor ( q ) ;
kfree ( q ) ;
2007-10-15 13:39:14 +04:00
}
EXPORT_SYMBOL ( inet_frag_destroy ) ;
2007-10-15 13:40:06 +04:00
2008-01-22 17:07:25 +03:00
int inet_frag_evictor ( struct netns_frags * nf , struct inet_frags * f )
2007-10-15 13:40:06 +04:00
{
struct inet_frag_queue * q ;
int work , evicted = 0 ;
2008-01-22 17:10:13 +03:00
work = atomic_read ( & nf - > mem ) - nf - > low_thresh ;
2007-10-15 13:40:06 +04:00
while ( work > 0 ) {
read_lock ( & f - > lock ) ;
2008-01-22 17:11:48 +03:00
if ( list_empty ( & nf - > lru_list ) ) {
2007-10-15 13:40:06 +04:00
read_unlock ( & f - > lock ) ;
break ;
}
2008-01-22 17:11:48 +03:00
q = list_first_entry ( & nf - > lru_list ,
2007-10-15 13:40:06 +04:00
struct inet_frag_queue , lru_list ) ;
atomic_inc ( & q - > refcnt ) ;
read_unlock ( & f - > lock ) ;
spin_lock ( & q - > lock ) ;
2008-03-29 02:35:27 +03:00
if ( ! ( q - > last_in & INET_FRAG_COMPLETE ) )
2007-10-15 13:40:06 +04:00
inet_frag_kill ( q , f ) ;
spin_unlock ( & q - > lock ) ;
if ( atomic_dec_and_test ( & q - > refcnt ) )
inet_frag_destroy ( q , f , & work ) ;
evicted + + ;
}
return evicted ;
}
EXPORT_SYMBOL ( inet_frag_evictor ) ;
2007-10-18 06:44:34 +04:00
2008-01-22 17:02:14 +03:00
static struct inet_frag_queue * inet_frag_intern ( struct netns_frags * nf ,
struct inet_frag_queue * qp_in , struct inet_frags * f ,
2008-06-28 07:06:08 +04:00
void * arg )
2007-10-18 06:44:34 +04:00
{
struct inet_frag_queue * qp ;
# ifdef CONFIG_SMP
struct hlist_node * n ;
# endif
2008-06-28 07:06:08 +04:00
unsigned int hash ;
2007-10-18 06:44:34 +04:00
write_lock ( & f - > lock ) ;
2008-06-28 07:06:08 +04:00
/*
* While we stayed w / o the lock other CPU could update
* the rnd seed , so we need to re - calculate the hash
* chain . Fortunatelly the qp_in can be used to get one .
*/
hash = f - > hashfn ( qp_in ) ;
2007-10-18 06:44:34 +04:00
# ifdef CONFIG_SMP
/* With SMP race we have to recheck hash table, because
* such entry could be created on other cpu , while we
* promoted read lock to write lock .
*/
hlist_for_each_entry ( qp , n , & f - > hash [ hash ] , list ) {
2008-01-22 17:02:14 +03:00
if ( qp - > net = = nf & & f - > match ( qp , arg ) ) {
2007-10-18 06:44:34 +04:00
atomic_inc ( & qp - > refcnt ) ;
write_unlock ( & f - > lock ) ;
2008-03-29 02:35:27 +03:00
qp_in - > last_in | = INET_FRAG_COMPLETE ;
2007-10-18 06:44:34 +04:00
inet_frag_put ( qp_in , f ) ;
return qp ;
}
}
# endif
qp = qp_in ;
2008-01-22 17:09:37 +03:00
if ( ! mod_timer ( & qp - > timer , jiffies + nf - > timeout ) )
2007-10-18 06:44:34 +04:00
atomic_inc ( & qp - > refcnt ) ;
atomic_inc ( & qp - > refcnt ) ;
hlist_add_head ( & qp - > list , & f - > hash [ hash ] ) ;
2008-01-22 17:11:48 +03:00
list_add_tail ( & qp - > lru_list , & nf - > lru_list ) ;
2008-01-22 17:06:23 +03:00
nf - > nqueues + + ;
2007-10-18 06:44:34 +04:00
write_unlock ( & f - > lock ) ;
return qp ;
}
2007-10-18 06:45:23 +04:00
2008-01-22 17:02:14 +03:00
static struct inet_frag_queue * inet_frag_alloc ( struct netns_frags * nf ,
struct inet_frags * f , void * arg )
2007-10-18 06:45:23 +04:00
{
struct inet_frag_queue * q ;
q = kzalloc ( f - > qsize , GFP_ATOMIC ) ;
if ( q = = NULL )
return NULL ;
2007-10-18 06:46:47 +04:00
f - > constructor ( q , arg ) ;
2008-01-22 17:07:25 +03:00
atomic_add ( f - > qsize , & nf - > mem ) ;
2007-10-18 06:45:23 +04:00
setup_timer ( & q - > timer , f - > frag_expire , ( unsigned long ) q ) ;
spin_lock_init ( & q - > lock ) ;
atomic_set ( & q - > refcnt , 1 ) ;
2008-01-22 17:02:14 +03:00
q - > net = nf ;
2007-10-18 06:45:23 +04:00
return q ;
}
2007-10-18 06:46:47 +04:00
2008-01-22 17:02:14 +03:00
static struct inet_frag_queue * inet_frag_create ( struct netns_frags * nf ,
2008-06-28 07:06:08 +04:00
struct inet_frags * f , void * arg )
2007-10-18 06:46:47 +04:00
{
struct inet_frag_queue * q ;
2008-01-22 17:02:14 +03:00
q = inet_frag_alloc ( nf , f , arg ) ;
2007-10-18 06:46:47 +04:00
if ( q = = NULL )
return NULL ;
2008-06-28 07:06:08 +04:00
return inet_frag_intern ( nf , q , f , arg ) ;
2007-10-18 06:46:47 +04:00
}
2007-10-18 06:47:21 +04:00
2008-01-22 17:02:14 +03:00
struct inet_frag_queue * inet_frag_find ( struct netns_frags * nf ,
struct inet_frags * f , void * key , unsigned int hash )
2009-02-25 13:32:52 +03:00
__releases ( & f - > lock )
2007-10-18 06:47:21 +04:00
{
struct inet_frag_queue * q ;
struct hlist_node * n ;
hlist_for_each_entry ( q , n , & f - > hash [ hash ] , list ) {
2008-01-22 17:02:14 +03:00
if ( q - > net = = nf & & f - > match ( q , key ) ) {
2007-10-18 06:47:21 +04:00
atomic_inc ( & q - > refcnt ) ;
read_unlock ( & f - > lock ) ;
return q ;
}
}
read_unlock ( & f - > lock ) ;
2008-06-28 07:06:08 +04:00
return inet_frag_create ( nf , f , key ) ;
2007-10-18 06:47:21 +04:00
}
EXPORT_SYMBOL ( inet_frag_find ) ;