2005-04-17 02:20:36 +04:00
/*
* Copyright ( C ) 2002 USAGI / WIDE Project
2007-02-09 17:24:49 +03:00
*
2005-04-17 02:20:36 +04:00
* 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 17:24:49 +03:00
*
2005-04-17 02:20:36 +04:00
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
2007-02-09 17:24:49 +03:00
*
2005-04-17 02:20:36 +04:00
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*
* Authors
*
2007-02-09 17:24:49 +03:00
* Mitsuru KANDA @ USAGI : IPv6 Support
2005-04-17 02:20:36 +04:00
* Kazunori MIYAZAWA @ USAGI :
* Kunihiro Ishiguro < kunihiro @ ipinfusion . com >
2007-02-09 17:24:49 +03:00
*
2005-04-17 02:20:36 +04:00
* This file is derived from net / ipv4 / esp . c
*/
2006-07-30 09:41:01 +04:00
# include <linux/err.h>
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# include <net/ip.h>
# include <net/xfrm.h>
# include <net/esp.h>
# include <asm/scatterlist.h>
# include <linux/crypto.h>
2005-10-11 08:11:08 +04:00
# include <linux/kernel.h>
2005-04-17 02:20:36 +04:00
# include <linux/pfkeyv2.h>
# include <linux/random.h>
2007-10-10 00:33:35 +04:00
# include <linux/spinlock.h>
2005-04-17 02:20:36 +04:00
# include <net/icmp.h>
# include <net/ipv6.h>
2005-12-27 07:43:12 +03:00
# include <net/protocol.h>
2005-04-17 02:20:36 +04:00
# include <linux/icmpv6.h>
static int esp6_output ( struct xfrm_state * x , struct sk_buff * skb )
{
int err ;
2007-10-11 02:45:25 +04:00
struct ip_esp_hdr * esph ;
2006-07-30 09:41:01 +04:00
struct crypto_blkcipher * tfm ;
struct blkcipher_desc desc ;
2005-04-17 02:20:36 +04:00
struct sk_buff * trailer ;
int blksize ;
int clen ;
int alen ;
int nfrags ;
2007-04-20 07:29:13 +04:00
u8 * tail ;
2007-04-26 04:55:53 +04:00
struct esp_data * esp = x - > data ;
2005-04-17 02:20:36 +04:00
2007-10-11 02:44:06 +04:00
/* skb is pure payload to encrypt */
2005-04-17 02:20:36 +04:00
err = - ENOMEM ;
/* Round to block size */
clen = skb - > len ;
alen = esp - > auth . icv_trunc_len ;
tfm = esp - > conf . tfm ;
2006-07-30 09:41:01 +04:00
desc . tfm = tfm ;
desc . flags = 0 ;
blksize = ALIGN ( crypto_blkcipher_blocksize ( tfm ) , 4 ) ;
2005-10-11 08:11:08 +04:00
clen = ALIGN ( clen + 2 , blksize ) ;
2005-04-17 02:20:36 +04:00
if ( esp - > conf . padlen )
2005-10-11 08:11:08 +04:00
clen = ALIGN ( clen , esp - > conf . padlen ) ;
2005-04-17 02:20:36 +04:00
if ( ( nfrags = skb_cow_data ( skb , clen - skb - > len + alen , & trailer ) ) < 0 ) {
goto error ;
}
/* Fill padding... */
2007-04-20 07:29:13 +04:00
tail = skb_tail_pointer ( trailer ) ;
2005-04-17 02:20:36 +04:00
do {
int i ;
for ( i = 0 ; i < clen - skb - > len - 2 ; i + + )
2007-04-20 07:29:13 +04:00
tail [ i ] = i + 1 ;
2005-04-17 02:20:36 +04:00
} while ( 0 ) ;
2007-04-20 07:29:13 +04:00
tail [ clen - skb - > len - 2 ] = ( clen - skb - > len ) - 2 ;
2005-04-17 02:20:36 +04:00
pskb_put ( skb , trailer , clen - skb - > len ) ;
2007-10-11 02:44:06 +04:00
skb_push ( skb , - skb_network_offset ( skb ) ) ;
2007-10-11 02:45:25 +04:00
esph = ip_esp_hdr ( skb ) ;
2007-10-10 00:25:59 +04:00
* ( skb_tail_pointer ( trailer ) - 1 ) = * skb_mac_header ( skb ) ;
* skb_mac_header ( skb ) = IPPROTO_ESP ;
2005-04-17 02:20:36 +04:00
esph - > spi = x - > id . spi ;
2007-10-09 04:25:53 +04:00
esph - > seq_no = htonl ( XFRM_SKB_CB ( skb ) - > seq ) ;
2005-04-17 02:20:36 +04:00
2007-10-10 00:33:35 +04:00
spin_lock_bh ( & x - > lock ) ;
2006-09-23 02:17:35 +04:00
if ( esp - > conf . ivlen ) {
if ( unlikely ( ! esp - > conf . ivinitted ) ) {
get_random_bytes ( esp - > conf . ivec , esp - > conf . ivlen ) ;
esp - > conf . ivinitted = 1 ;
}
2006-07-30 09:41:01 +04:00
crypto_blkcipher_set_iv ( tfm , esp - > conf . ivec , esp - > conf . ivlen ) ;
2006-09-23 02:17:35 +04:00
}
2005-04-17 02:20:36 +04:00
do {
struct scatterlist * sg = & esp - > sgbuf [ 0 ] ;
if ( unlikely ( nfrags > ESP_NUM_FAST_SG ) ) {
sg = kmalloc ( sizeof ( struct scatterlist ) * nfrags , GFP_ATOMIC ) ;
if ( ! sg )
2007-10-10 00:33:35 +04:00
goto unlock ;
2005-04-17 02:20:36 +04:00
}
skb_to_sgvec ( skb , sg , esph - > enc_data + esp - > conf . ivlen - skb - > data , clen ) ;
2006-07-30 09:41:01 +04:00
err = crypto_blkcipher_encrypt ( & desc , sg , sg , clen ) ;
2005-04-17 02:20:36 +04:00
if ( unlikely ( sg ! = & esp - > sgbuf [ 0 ] ) )
kfree ( sg ) ;
} while ( 0 ) ;
2006-07-30 09:41:01 +04:00
if ( unlikely ( err ) )
2007-10-10 00:33:35 +04:00
goto unlock ;
2006-07-30 09:41:01 +04:00
2005-04-17 02:20:36 +04:00
if ( esp - > conf . ivlen ) {
2006-07-30 09:41:01 +04:00
memcpy ( esph - > enc_data , esp - > conf . ivec , esp - > conf . ivlen ) ;
crypto_blkcipher_get_iv ( tfm , esp - > conf . ivec , esp - > conf . ivlen ) ;
2005-04-17 02:20:36 +04:00
}
if ( esp - > auth . icv_full_len ) {
2006-08-20 08:24:50 +04:00
err = esp_mac_digest ( esp , skb , ( u8 * ) esph - skb - > data ,
sizeof ( * esph ) + esp - > conf . ivlen + clen ) ;
memcpy ( pskb_put ( skb , trailer , alen ) , esp - > auth . work_icv , alen ) ;
2005-04-17 02:20:36 +04:00
}
2007-10-10 00:33:35 +04:00
unlock :
spin_unlock_bh ( & x - > lock ) ;
2005-04-17 02:20:36 +04:00
error :
return err ;
}
2006-04-01 12:52:46 +04:00
static int esp6_input ( struct xfrm_state * x , struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
struct ipv6hdr * iph ;
2007-10-11 02:45:25 +04:00
struct ip_esp_hdr * esph ;
2005-04-17 02:20:36 +04:00
struct esp_data * esp = x - > data ;
2006-07-30 09:41:01 +04:00
struct crypto_blkcipher * tfm = esp - > conf . tfm ;
struct blkcipher_desc desc = { . tfm = tfm } ;
2005-04-17 02:20:36 +04:00
struct sk_buff * trailer ;
2006-07-30 09:41:01 +04:00
int blksize = ALIGN ( crypto_blkcipher_blocksize ( tfm ) , 4 ) ;
2005-04-17 02:20:36 +04:00
int alen = esp - > auth . icv_trunc_len ;
2007-10-11 02:45:25 +04:00
int elen = skb - > len - sizeof ( * esph ) - esp - > conf . ivlen - alen ;
2007-03-16 23:26:39 +03:00
int hdr_len = skb_network_header_len ( skb ) ;
2005-04-17 02:20:36 +04:00
int nfrags ;
int ret = 0 ;
2007-10-11 02:45:25 +04:00
if ( ! pskb_may_pull ( skb , sizeof ( * esph ) ) ) {
2005-04-17 02:20:36 +04:00
ret = - EINVAL ;
2006-05-28 10:06:13 +04:00
goto out ;
2005-04-17 02:20:36 +04:00
}
if ( elen < = 0 | | ( elen & ( blksize - 1 ) ) ) {
ret = - EINVAL ;
2006-05-28 10:06:13 +04:00
goto out ;
2005-04-17 02:20:36 +04:00
}
/* If integrity check is required, do this. */
2007-02-09 17:24:49 +03:00
if ( esp - > auth . icv_full_len ) {
2006-08-20 08:24:50 +04:00
u8 sum [ alen ] ;
2005-04-17 02:20:36 +04:00
2006-08-20 08:24:50 +04:00
ret = esp_mac_digest ( esp , skb , 0 , skb - > len - alen ) ;
if ( ret )
goto out ;
2005-04-17 02:20:36 +04:00
2006-08-20 08:24:50 +04:00
if ( skb_copy_bits ( skb , skb - > len - alen , sum , alen ) )
2005-04-17 02:20:36 +04:00
BUG ( ) ;
2006-08-20 08:24:50 +04:00
if ( unlikely ( memcmp ( esp - > auth . work_icv , sum , alen ) ) ) {
2005-04-17 02:20:36 +04:00
x - > stats . integrity_failed + + ;
ret = - EINVAL ;
goto out ;
}
}
if ( ( nfrags = skb_cow_data ( skb , 0 , & trailer ) ) < 0 ) {
ret = - EINVAL ;
goto out ;
}
skb - > ip_summed = CHECKSUM_NONE ;
2007-10-11 02:45:25 +04:00
esph = ( struct ip_esp_hdr * ) skb - > data ;
2007-04-26 04:54:47 +04:00
iph = ipv6_hdr ( skb ) ;
2005-04-17 02:20:36 +04:00
/* Get ivec. This can be wrong, check against another impls. */
if ( esp - > conf . ivlen )
2006-07-30 09:41:01 +04:00
crypto_blkcipher_set_iv ( tfm , esph - > enc_data , esp - > conf . ivlen ) ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:49 +03:00
{
2005-04-17 02:20:36 +04:00
u8 nexthdr [ 2 ] ;
struct scatterlist * sg = & esp - > sgbuf [ 0 ] ;
u8 padlen ;
if ( unlikely ( nfrags > ESP_NUM_FAST_SG ) ) {
sg = kmalloc ( sizeof ( struct scatterlist ) * nfrags , GFP_ATOMIC ) ;
if ( ! sg ) {
ret = - ENOMEM ;
goto out ;
}
}
2007-10-11 02:45:25 +04:00
skb_to_sgvec ( skb , sg , sizeof ( * esph ) + esp - > conf . ivlen , elen ) ;
2006-07-30 09:41:01 +04:00
ret = crypto_blkcipher_decrypt ( & desc , sg , sg , elen ) ;
2005-04-17 02:20:36 +04:00
if ( unlikely ( sg ! = & esp - > sgbuf [ 0 ] ) )
kfree ( sg ) ;
2006-07-30 09:41:01 +04:00
if ( unlikely ( ret ) )
goto out ;
2005-04-17 02:20:36 +04:00
if ( skb_copy_bits ( skb , skb - > len - alen - 2 , nexthdr , 2 ) )
BUG ( ) ;
padlen = nexthdr [ 0 ] ;
if ( padlen + 2 > = elen ) {
2005-08-10 07:50:53 +04:00
LIMIT_NETDEBUG ( KERN_WARNING " ipsec esp packet is garbage padlen=%d, elen=%d \n " , padlen + 2 , elen ) ;
2005-04-17 02:20:36 +04:00
ret = - EINVAL ;
goto out ;
}
2007-02-09 17:24:49 +03:00
/* ... check padding bits here. Silly. :-) */
2005-04-17 02:20:36 +04:00
pskb_trim ( skb , skb - > len - alen - padlen - 2 ) ;
ret = nexthdr [ 1 ] ;
}
2007-03-13 19:51:52 +03:00
__skb_pull ( skb , sizeof ( * esph ) + esp - > conf . ivlen ) ;
skb_set_transport_header ( skb , - hdr_len ) ;
2005-04-17 02:20:36 +04:00
out :
return ret ;
}
2007-04-09 22:47:18 +04:00
static u32 esp6_get_mtu ( struct xfrm_state * x , int mtu )
2005-04-17 02:20:36 +04:00
{
struct esp_data * esp = x - > data ;
2006-07-30 09:41:01 +04:00
u32 blksize = ALIGN ( crypto_blkcipher_blocksize ( esp - > conf . tfm ) , 4 ) ;
2007-04-09 22:47:18 +04:00
u32 align = max_t ( u32 , blksize , esp - > conf . padlen ) ;
u32 rem ;
2005-04-17 02:20:36 +04:00
2007-04-09 22:47:18 +04:00
mtu - = x - > props . header_len + esp - > auth . icv_trunc_len ;
rem = mtu & ( align - 1 ) ;
mtu & = ~ ( align - 1 ) ;
if ( x - > props . mode ! = XFRM_MODE_TUNNEL ) {
2005-10-11 08:11:34 +04:00
u32 padsize = ( ( blksize - 1 ) & 7 ) + 1 ;
2007-04-09 22:47:18 +04:00
mtu - = blksize - padsize ;
mtu + = min_t ( u32 , blksize - padsize , rem ) ;
2005-04-17 02:20:36 +04:00
}
2007-04-09 22:47:18 +04:00
return mtu - 2 ;
2005-04-17 02:20:36 +04:00
}
static void esp6_err ( struct sk_buff * skb , struct inet6_skb_parm * opt ,
2007-02-09 17:24:49 +03:00
int type , int code , int offset , __be32 info )
2005-04-17 02:20:36 +04:00
{
struct ipv6hdr * iph = ( struct ipv6hdr * ) skb - > data ;
2007-10-11 02:45:25 +04:00
struct ip_esp_hdr * esph = ( struct ip_esp_hdr * ) ( skb - > data + offset ) ;
2005-04-17 02:20:36 +04:00
struct xfrm_state * x ;
2007-02-09 17:24:49 +03:00
if ( type ! = ICMPV6_DEST_UNREACH & &
2005-04-17 02:20:36 +04:00
type ! = ICMPV6_PKT_TOOBIG )
return ;
x = xfrm_state_lookup ( ( xfrm_address_t * ) & iph - > daddr , esph - > spi , IPPROTO_ESP , AF_INET6 ) ;
if ( ! x )
return ;
2007-02-09 17:24:49 +03:00
printk ( KERN_DEBUG " pmtu discovery on SA ESP/%08x/ " NIP6_FMT " \n " ,
2005-04-17 02:20:36 +04:00
ntohl ( esph - > spi ) , NIP6 ( iph - > daddr ) ) ;
xfrm_state_put ( x ) ;
}
static void esp6_destroy ( struct xfrm_state * x )
{
struct esp_data * esp = x - > data ;
if ( ! esp )
return ;
2006-07-30 09:41:01 +04:00
crypto_free_blkcipher ( esp - > conf . tfm ) ;
2005-09-02 04:44:29 +04:00
esp - > conf . tfm = NULL ;
kfree ( esp - > conf . ivec ) ;
esp - > conf . ivec = NULL ;
2006-08-20 08:24:50 +04:00
crypto_free_hash ( esp - > auth . tfm ) ;
2005-09-02 04:44:29 +04:00
esp - > auth . tfm = NULL ;
kfree ( esp - > auth . work_icv ) ;
esp - > auth . work_icv = NULL ;
2005-04-17 02:20:36 +04:00
kfree ( esp ) ;
}
2005-06-21 00:18:08 +04:00
static int esp6_init_state ( struct xfrm_state * x )
2005-04-17 02:20:36 +04:00
{
struct esp_data * esp = NULL ;
2006-07-30 09:41:01 +04:00
struct crypto_blkcipher * tfm ;
2005-04-17 02:20:36 +04:00
if ( x - > ealg = = NULL )
goto error ;
if ( x - > encap )
goto error ;
2006-03-21 10:01:32 +03:00
esp = kzalloc ( sizeof ( * esp ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
if ( esp = = NULL )
return - ENOMEM ;
if ( x - > aalg ) {
struct xfrm_algo_desc * aalg_desc ;
2006-08-20 08:24:50 +04:00
struct crypto_hash * hash ;
2005-04-17 02:20:36 +04:00
2006-08-20 08:24:50 +04:00
hash = crypto_alloc_hash ( x - > aalg - > alg_name , 0 ,
CRYPTO_ALG_ASYNC ) ;
if ( IS_ERR ( hash ) )
goto error ;
esp - > auth . tfm = hash ;
2007-10-09 04:13:44 +04:00
if ( crypto_hash_setkey ( hash , x - > aalg - > alg_key ,
( x - > aalg - > alg_key_len + 7 ) / 8 ) )
2005-04-17 02:20:36 +04:00
goto error ;
2007-02-09 17:24:49 +03:00
2005-04-17 02:20:36 +04:00
aalg_desc = xfrm_aalg_get_byname ( x - > aalg - > alg_name , 0 ) ;
BUG_ON ( ! aalg_desc ) ;
2007-02-09 17:24:49 +03:00
2005-04-17 02:20:36 +04:00
if ( aalg_desc - > uinfo . auth . icv_fullbits / 8 ! =
2006-08-20 08:24:50 +04:00
crypto_hash_digestsize ( hash ) ) {
NETDEBUG ( KERN_INFO " ESP: %s digestsize %u != %hu \n " ,
x - > aalg - > alg_name ,
crypto_hash_digestsize ( hash ) ,
aalg_desc - > uinfo . auth . icv_fullbits / 8 ) ;
goto error ;
2005-04-17 02:20:36 +04:00
}
2007-02-09 17:24:49 +03:00
2005-04-17 02:20:36 +04:00
esp - > auth . icv_full_len = aalg_desc - > uinfo . auth . icv_fullbits / 8 ;
esp - > auth . icv_trunc_len = aalg_desc - > uinfo . auth . icv_truncbits / 8 ;
2007-02-09 17:24:49 +03:00
2005-04-17 02:20:36 +04:00
esp - > auth . work_icv = kmalloc ( esp - > auth . icv_full_len , GFP_KERNEL ) ;
if ( ! esp - > auth . work_icv )
goto error ;
}
2006-07-30 09:41:01 +04:00
tfm = crypto_alloc_blkcipher ( x - > ealg - > alg_name , 0 , CRYPTO_ALG_ASYNC ) ;
if ( IS_ERR ( tfm ) )
2005-04-17 02:20:36 +04:00
goto error ;
2006-07-30 09:41:01 +04:00
esp - > conf . tfm = tfm ;
esp - > conf . ivlen = crypto_blkcipher_ivsize ( tfm ) ;
2005-04-17 02:20:36 +04:00
esp - > conf . padlen = 0 ;
if ( esp - > conf . ivlen ) {
esp - > conf . ivec = kmalloc ( esp - > conf . ivlen , GFP_KERNEL ) ;
if ( unlikely ( esp - > conf . ivec = = NULL ) )
goto error ;
2006-09-23 02:17:35 +04:00
esp - > conf . ivinitted = 0 ;
2005-04-17 02:20:36 +04:00
}
2007-10-09 04:13:44 +04:00
if ( crypto_blkcipher_setkey ( tfm , x - > ealg - > alg_key ,
( x - > ealg - > alg_key_len + 7 ) / 8 ) )
2005-04-17 02:20:36 +04:00
goto error ;
2007-10-11 02:45:25 +04:00
x - > props . header_len = sizeof ( struct ip_esp_hdr ) + esp - > conf . ivlen ;
2007-10-18 08:35:15 +04:00
switch ( x - > props . mode ) {
case XFRM_MODE_BEET :
case XFRM_MODE_TRANSPORT :
break ;
case XFRM_MODE_TUNNEL :
2005-04-17 02:20:36 +04:00
x - > props . header_len + = sizeof ( struct ipv6hdr ) ;
2007-10-18 08:35:15 +04:00
default :
goto error ;
}
2005-04-17 02:20:36 +04:00
x - > data = esp ;
return 0 ;
error :
x - > data = esp ;
esp6_destroy ( x ) ;
x - > data = NULL ;
return - EINVAL ;
}
static struct xfrm_type esp6_type =
{
. description = " ESP6 " ,
. owner = THIS_MODULE ,
. proto = IPPROTO_ESP ,
2007-10-09 04:25:53 +04:00
. flags = XFRM_TYPE_REPLAY_PROT ,
2005-04-17 02:20:36 +04:00
. init_state = esp6_init_state ,
. destructor = esp6_destroy ,
2007-04-09 22:47:18 +04:00
. get_mtu = esp6_get_mtu ,
2005-04-17 02:20:36 +04:00
. input = esp6_input ,
2006-08-24 04:57:28 +04:00
. output = esp6_output ,
. hdr_offset = xfrm6_find_1stfragopt ,
2005-04-17 02:20:36 +04:00
} ;
static struct inet6_protocol esp6_protocol = {
. handler = xfrm6_rcv ,
. err_handler = esp6_err ,
. flags = INET6_PROTO_NOPOLICY ,
} ;
static int __init esp6_init ( void )
{
if ( xfrm_register_type ( & esp6_type , AF_INET6 ) < 0 ) {
printk ( KERN_INFO " ipv6 esp init: can't add xfrm type \n " ) ;
return - EAGAIN ;
}
if ( inet6_add_protocol ( & esp6_protocol , IPPROTO_ESP ) < 0 ) {
printk ( KERN_INFO " ipv6 esp init: can't add protocol \n " ) ;
xfrm_unregister_type ( & esp6_type , AF_INET6 ) ;
return - EAGAIN ;
}
return 0 ;
}
static void __exit esp6_fini ( void )
{
if ( inet6_del_protocol ( & esp6_protocol , IPPROTO_ESP ) < 0 )
printk ( KERN_INFO " ipv6 esp close: can't remove protocol \n " ) ;
if ( xfrm_unregister_type ( & esp6_type , AF_INET6 ) < 0 )
printk ( KERN_INFO " ipv6 esp close: can't remove xfrm type \n " ) ;
}
module_init ( esp6_init ) ;
module_exit ( esp6_fini ) ;
MODULE_LICENSE ( " GPL " ) ;
2007-06-27 10:57:49 +04:00
MODULE_ALIAS_XFRM_TYPE ( AF_INET6 , XFRM_PROTO_ESP ) ;