2008-07-25 13:54:40 +04:00
/*
* IP Payload Compression Protocol ( IPComp ) - RFC3173 .
*
* Copyright ( c ) 2003 James Morris < jmorris @ intercode . com . au >
* Copyright ( c ) 2003 - 2008 Herbert Xu < herbert @ gondor . apana . org . au >
*
* 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 .
*
* Todo :
* - Tunable compression parameters .
* - Compression stats .
* - Adaptive compression .
*/
# include <linux/crypto.h>
# include <linux/err.h>
2008-07-25 13:55:33 +04:00
# include <linux/gfp.h>
2008-07-25 13:54:40 +04:00
# include <linux/list.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/percpu.h>
# include <linux/smp.h>
# include <linux/vmalloc.h>
# include <net/ip.h>
# include <net/ipcomp.h>
# include <net/xfrm.h>
struct ipcomp_tfms {
struct list_head list ;
2010-02-16 18:20:26 +03:00
struct crypto_comp * __percpu * tfms ;
2008-07-25 13:54:40 +04:00
int users ;
} ;
static DEFINE_MUTEX ( ipcomp_resource_mutex ) ;
2010-02-16 18:20:26 +03:00
static void * __percpu * ipcomp_scratches ;
2008-07-25 13:54:40 +04:00
static int ipcomp_scratch_users ;
static LIST_HEAD ( ipcomp_tfms_list ) ;
static int ipcomp_decompress ( struct xfrm_state * x , struct sk_buff * skb )
{
struct ipcomp_data * ipcd = x - > data ;
const int plen = skb - > len ;
int dlen = IPCOMP_SCRATCH_SIZE ;
const u8 * start = skb - > data ;
const int cpu = get_cpu ( ) ;
u8 * scratch = * per_cpu_ptr ( ipcomp_scratches , cpu ) ;
struct crypto_comp * tfm = * per_cpu_ptr ( ipcd - > tfms , cpu ) ;
int err = crypto_comp_decompress ( tfm , start , plen , scratch , & dlen ) ;
2008-07-25 13:55:33 +04:00
int len ;
2008-07-25 13:54:40 +04:00
if ( err )
goto out ;
if ( dlen < ( plen + sizeof ( struct ip_comp_hdr ) ) ) {
err = - EINVAL ;
goto out ;
}
2008-07-25 13:55:33 +04:00
len = dlen - plen ;
if ( len > skb_tailroom ( skb ) )
len = skb_tailroom ( skb ) ;
__skb_put ( skb , len ) ;
len + = plen ;
skb_copy_to_linear_data ( skb , scratch , len ) ;
while ( ( scratch + = len , dlen - = len ) > 0 ) {
skb_frag_t * frag ;
err = - EMSGSIZE ;
if ( WARN_ON ( skb_shinfo ( skb ) - > nr_frags > = MAX_SKB_FRAGS ) )
goto out ;
frag = skb_shinfo ( skb ) - > frags + skb_shinfo ( skb ) - > nr_frags ;
frag - > page = alloc_page ( GFP_ATOMIC ) ;
err = - ENOMEM ;
if ( ! frag - > page )
goto out ;
len = PAGE_SIZE ;
if ( dlen < len )
len = dlen ;
memcpy ( page_address ( frag - > page ) , scratch , len ) ;
frag - > page_offset = 0 ;
frag - > size = len ;
skb - > truesize + = len ;
skb - > data_len + = len ;
skb - > len + = len ;
skb_shinfo ( skb ) - > nr_frags + + ;
}
err = 0 ;
2008-07-25 13:54:40 +04:00
out :
put_cpu ( ) ;
return err ;
}
int ipcomp_input ( struct xfrm_state * x , struct sk_buff * skb )
{
int nexthdr ;
int err = - ENOMEM ;
struct ip_comp_hdr * ipch ;
if ( skb_linearize_cow ( skb ) )
goto out ;
skb - > ip_summed = CHECKSUM_NONE ;
/* Remove ipcomp header and decompress original payload */
ipch = ( void * ) skb - > data ;
nexthdr = ipch - > nexthdr ;
skb - > transport_header = skb - > network_header + sizeof ( * ipch ) ;
__skb_pull ( skb , sizeof ( * ipch ) ) ;
err = ipcomp_decompress ( x , skb ) ;
if ( err )
goto out ;
err = nexthdr ;
out :
return err ;
}
EXPORT_SYMBOL_GPL ( ipcomp_input ) ;
static int ipcomp_compress ( struct xfrm_state * x , struct sk_buff * skb )
{
struct ipcomp_data * ipcd = x - > data ;
const int plen = skb - > len ;
int dlen = IPCOMP_SCRATCH_SIZE ;
u8 * start = skb - > data ;
const int cpu = get_cpu ( ) ;
u8 * scratch = * per_cpu_ptr ( ipcomp_scratches , cpu ) ;
struct crypto_comp * tfm = * per_cpu_ptr ( ipcd - > tfms , cpu ) ;
int err ;
local_bh_disable ( ) ;
err = crypto_comp_compress ( tfm , start , plen , scratch , & dlen ) ;
local_bh_enable ( ) ;
if ( err )
goto out ;
if ( ( dlen + sizeof ( struct ip_comp_hdr ) ) > = plen ) {
err = - EMSGSIZE ;
goto out ;
}
memcpy ( start + sizeof ( struct ip_comp_hdr ) , scratch , dlen ) ;
put_cpu ( ) ;
pskb_trim ( skb , dlen + sizeof ( struct ip_comp_hdr ) ) ;
return 0 ;
out :
put_cpu ( ) ;
return err ;
}
int ipcomp_output ( struct xfrm_state * x , struct sk_buff * skb )
{
int err ;
struct ip_comp_hdr * ipch ;
struct ipcomp_data * ipcd = x - > data ;
if ( skb - > len < ipcd - > threshold ) {
/* Don't bother compressing */
goto out_ok ;
}
if ( skb_linearize_cow ( skb ) )
goto out_ok ;
err = ipcomp_compress ( x , skb ) ;
if ( err ) {
goto out_ok ;
}
/* Install ipcomp header, convert into ipcomp datagram. */
ipch = ip_comp_hdr ( skb ) ;
ipch - > nexthdr = * skb_mac_header ( skb ) ;
ipch - > flags = 0 ;
ipch - > cpi = htons ( ( u16 ) ntohl ( x - > id . spi ) ) ;
* skb_mac_header ( skb ) = IPPROTO_COMP ;
out_ok :
skb_push ( skb , - skb_network_offset ( skb ) ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( ipcomp_output ) ;
static void ipcomp_free_scratches ( void )
{
int i ;
2010-02-16 18:20:26 +03:00
void * __percpu * scratches ;
2008-07-25 13:54:40 +04:00
if ( - - ipcomp_scratch_users )
return ;
scratches = ipcomp_scratches ;
if ( ! scratches )
return ;
for_each_possible_cpu ( i )
vfree ( * per_cpu_ptr ( scratches , i ) ) ;
free_percpu ( scratches ) ;
}
2010-02-16 18:20:26 +03:00
static void * __percpu * ipcomp_alloc_scratches ( void )
2008-07-25 13:54:40 +04:00
{
int i ;
2010-02-16 18:20:26 +03:00
void * __percpu * scratches ;
2008-07-25 13:54:40 +04:00
if ( ipcomp_scratch_users + + )
return ipcomp_scratches ;
scratches = alloc_percpu ( void * ) ;
if ( ! scratches )
return NULL ;
ipcomp_scratches = scratches ;
for_each_possible_cpu ( i ) {
void * scratch = vmalloc ( IPCOMP_SCRATCH_SIZE ) ;
if ( ! scratch )
return NULL ;
* per_cpu_ptr ( scratches , i ) = scratch ;
}
return scratches ;
}
2010-02-16 18:20:26 +03:00
static void ipcomp_free_tfms ( struct crypto_comp * __percpu * tfms )
2008-07-25 13:54:40 +04:00
{
struct ipcomp_tfms * pos ;
int cpu ;
list_for_each_entry ( pos , & ipcomp_tfms_list , list ) {
if ( pos - > tfms = = tfms )
break ;
}
2008-07-26 08:43:18 +04:00
WARN_ON ( ! pos ) ;
2008-07-25 13:54:40 +04:00
if ( - - pos - > users )
return ;
list_del ( & pos - > list ) ;
kfree ( pos ) ;
if ( ! tfms )
return ;
for_each_possible_cpu ( cpu ) {
struct crypto_comp * tfm = * per_cpu_ptr ( tfms , cpu ) ;
crypto_free_comp ( tfm ) ;
}
free_percpu ( tfms ) ;
}
2010-02-16 18:20:26 +03:00
static struct crypto_comp * __percpu * ipcomp_alloc_tfms ( const char * alg_name )
2008-07-25 13:54:40 +04:00
{
struct ipcomp_tfms * pos ;
2010-02-16 18:20:26 +03:00
struct crypto_comp * __percpu * tfms ;
2008-07-25 13:54:40 +04:00
int cpu ;
/* This can be any valid CPU ID so we don't need locking. */
cpu = raw_smp_processor_id ( ) ;
list_for_each_entry ( pos , & ipcomp_tfms_list , list ) {
struct crypto_comp * tfm ;
tfms = pos - > tfms ;
tfm = * per_cpu_ptr ( tfms , cpu ) ;
if ( ! strcmp ( crypto_comp_name ( tfm ) , alg_name ) ) {
pos - > users + + ;
return tfms ;
}
}
pos = kmalloc ( sizeof ( * pos ) , GFP_KERNEL ) ;
if ( ! pos )
return NULL ;
pos - > users = 1 ;
INIT_LIST_HEAD ( & pos - > list ) ;
list_add ( & pos - > list , & ipcomp_tfms_list ) ;
pos - > tfms = tfms = alloc_percpu ( struct crypto_comp * ) ;
if ( ! tfms )
goto error ;
for_each_possible_cpu ( cpu ) {
struct crypto_comp * tfm = crypto_alloc_comp ( alg_name , 0 ,
CRYPTO_ALG_ASYNC ) ;
if ( IS_ERR ( tfm ) )
goto error ;
* per_cpu_ptr ( tfms , cpu ) = tfm ;
}
return tfms ;
error :
ipcomp_free_tfms ( tfms ) ;
return NULL ;
}
static void ipcomp_free_data ( struct ipcomp_data * ipcd )
{
if ( ipcd - > tfms )
ipcomp_free_tfms ( ipcd - > tfms ) ;
ipcomp_free_scratches ( ) ;
}
void ipcomp_destroy ( struct xfrm_state * x )
{
struct ipcomp_data * ipcd = x - > data ;
if ( ! ipcd )
return ;
xfrm_state_delete_tunnel ( x ) ;
mutex_lock ( & ipcomp_resource_mutex ) ;
ipcomp_free_data ( ipcd ) ;
mutex_unlock ( & ipcomp_resource_mutex ) ;
kfree ( ipcd ) ;
}
EXPORT_SYMBOL_GPL ( ipcomp_destroy ) ;
int ipcomp_init_state ( struct xfrm_state * x )
{
int err ;
struct ipcomp_data * ipcd ;
struct xfrm_algo_desc * calg_desc ;
err = - EINVAL ;
if ( ! x - > calg )
goto out ;
if ( x - > encap )
goto out ;
err = - ENOMEM ;
ipcd = kzalloc ( sizeof ( * ipcd ) , GFP_KERNEL ) ;
if ( ! ipcd )
goto out ;
mutex_lock ( & ipcomp_resource_mutex ) ;
if ( ! ipcomp_alloc_scratches ( ) )
goto error ;
ipcd - > tfms = ipcomp_alloc_tfms ( x - > calg - > alg_name ) ;
if ( ! ipcd - > tfms )
goto error ;
mutex_unlock ( & ipcomp_resource_mutex ) ;
calg_desc = xfrm_calg_get_byname ( x - > calg - > alg_name , 0 ) ;
BUG_ON ( ! calg_desc ) ;
ipcd - > threshold = calg_desc - > uinfo . comp . threshold ;
x - > data = ipcd ;
err = 0 ;
out :
return err ;
error :
ipcomp_free_data ( ipcd ) ;
mutex_unlock ( & ipcomp_resource_mutex ) ;
kfree ( ipcd ) ;
goto out ;
}
EXPORT_SYMBOL_GPL ( ipcomp_init_state ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_DESCRIPTION ( " IP Payload Compression Protocol (IPComp) - RFC3173 " ) ;
MODULE_AUTHOR ( " James Morris <jmorris@intercode.com.au> " ) ;