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>
# include <net/icmp.h>
2005-12-27 07:43:12 +03:00
# include <net/protocol.h>
2005-04-17 02:20:36 +04:00
# include <net/udp.h>
static int esp_output ( struct xfrm_state * x , struct sk_buff * skb )
{
int err ;
struct iphdr * top_iph ;
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 esp_data * esp ;
struct sk_buff * trailer ;
int blksize ;
int clen ;
int alen ;
int nfrags ;
/* Strip IP+ESP header. */
2007-04-26 04:55:53 +04:00
__skb_pull ( skb , skb_transport_offset ( skb ) ) ;
2005-04-17 02:20:36 +04:00
/* Now skb is pure payload to encrypt */
err = - ENOMEM ;
/* Round to block size */
clen = skb - > len ;
esp = x - > data ;
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... */
do {
int i ;
for ( i = 0 ; i < clen - skb - > len - 2 ; i + + )
* ( u8 * ) ( trailer - > tail + i ) = i + 1 ;
} while ( 0 ) ;
* ( u8 * ) ( trailer - > tail + clen - skb - > len - 2 ) = ( clen - skb - > len ) - 2 ;
pskb_put ( skb , trailer , clen - skb - > len ) ;
2007-04-11 07:50:43 +04:00
__skb_push ( skb , skb - > data - skb_network_header ( skb ) ) ;
2007-04-21 09:47:35 +04:00
top_iph = ip_hdr ( skb ) ;
2007-04-11 07:50:43 +04:00
esph = ( struct ip_esp_hdr * ) ( skb_network_header ( skb ) +
top_iph - > ihl * 4 ) ;
2005-04-17 02:20:36 +04:00
top_iph - > tot_len = htons ( skb - > len + alen ) ;
* ( u8 * ) ( trailer - > tail - 1 ) = top_iph - > protocol ;
/* this is non-NULL only with UDP Encapsulation */
if ( x - > encap ) {
struct xfrm_encap_tmpl * encap = x - > encap ;
struct udphdr * uh ;
2006-11-08 11:23:14 +03:00
__be32 * udpdata32 ;
2005-04-17 02:20:36 +04:00
uh = ( struct udphdr * ) esph ;
uh - > source = encap - > encap_sport ;
uh - > dest = encap - > encap_dport ;
uh - > len = htons ( skb - > len + alen - top_iph - > ihl * 4 ) ;
uh - > check = 0 ;
switch ( encap - > encap_type ) {
default :
case UDP_ENCAP_ESPINUDP :
esph = ( struct ip_esp_hdr * ) ( uh + 1 ) ;
break ;
case UDP_ENCAP_ESPINUDP_NON_IKE :
2006-11-08 11:23:14 +03:00
udpdata32 = ( __be32 * ) ( uh + 1 ) ;
2005-04-17 02:20:36 +04:00
udpdata32 [ 0 ] = udpdata32 [ 1 ] = 0 ;
esph = ( struct ip_esp_hdr * ) ( udpdata32 + 2 ) ;
break ;
}
top_iph - > protocol = IPPROTO_UDP ;
} else
top_iph - > protocol = IPPROTO_ESP ;
esph - > spi = x - > id . spi ;
esph - > seq_no = htonl ( + + x - > replay . oseq ) ;
2006-03-21 06:15:29 +03:00
xfrm_aevent_doreplay ( x ) ;
2005-04-17 02:20:36 +04:00
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 )
goto error ;
}
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 ) )
goto error ;
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
}
ip_send_check ( top_iph ) ;
error :
return err ;
}
/*
* Note : detecting truncated vs . non - truncated authentication data is very
* expensive , so we only support truncated data , which is the recommended
* and common case .
*/
2006-04-01 12:52:46 +04:00
static int esp_input ( struct xfrm_state * x , struct sk_buff * skb )
2005-04-17 02:20:36 +04:00
{
struct iphdr * iph ;
struct ip_esp_hdr * esph ;
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 ;
int elen = skb - > len - sizeof ( struct ip_esp_hdr ) - esp - > conf . ivlen - alen ;
int nfrags ;
2006-05-28 10:06:13 +04:00
int ihl ;
2006-02-28 00:00:01 +03:00
u8 nexthdr [ 2 ] ;
struct scatterlist * sg ;
int padlen ;
2006-07-30 09:41:01 +04:00
int err ;
2005-04-17 02:20:36 +04:00
if ( ! pskb_may_pull ( skb , sizeof ( struct ip_esp_hdr ) ) )
goto out ;
if ( elen < = 0 | | ( elen & ( blksize - 1 ) ) )
goto out ;
/* If integrity check is required, do this. */
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
err = esp_mac_digest ( esp , skb , 0 , skb - > len - alen ) ;
if ( err )
goto out ;
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 + + ;
goto out ;
}
}
if ( ( nfrags = skb_cow_data ( skb , 0 , & trailer ) ) < 0 )
goto out ;
skb - > ip_summed = CHECKSUM_NONE ;
esph = ( struct ip_esp_hdr * ) skb - > data ;
/* 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
2006-02-28 00:00:01 +03:00
sg = & esp - > sgbuf [ 0 ] ;
2005-04-17 02:20:36 +04:00
2006-02-28 00:00:01 +03:00
if ( unlikely ( nfrags > ESP_NUM_FAST_SG ) ) {
sg = kmalloc ( sizeof ( struct scatterlist ) * nfrags , GFP_ATOMIC ) ;
if ( ! sg )
goto out ;
}
skb_to_sgvec ( skb , sg , sizeof ( struct ip_esp_hdr ) + esp - > conf . ivlen , elen ) ;
2006-07-30 09:41:01 +04:00
err = crypto_blkcipher_decrypt ( & desc , sg , sg , elen ) ;
2006-02-28 00:00:01 +03:00
if ( unlikely ( sg ! = & esp - > sgbuf [ 0 ] ) )
kfree ( sg ) ;
2006-07-30 09:41:01 +04:00
if ( unlikely ( err ) )
return err ;
2005-04-17 02:20:36 +04:00
2006-02-28 00:00:01 +03:00
if ( skb_copy_bits ( skb , skb - > len - alen - 2 , nexthdr , 2 ) )
BUG ( ) ;
2005-04-17 02:20:36 +04:00
2006-02-28 00:00:01 +03:00
padlen = nexthdr [ 0 ] ;
if ( padlen + 2 > = elen )
goto out ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:47 +03:00
/* ... check padding bits here. Silly. :-) */
2005-04-17 02:20:36 +04:00
2007-04-21 09:47:35 +04:00
iph = ip_hdr ( skb ) ;
2006-05-28 10:06:13 +04:00
ihl = iph - > ihl * 4 ;
2006-02-28 00:00:40 +03:00
if ( x - > encap ) {
struct xfrm_encap_tmpl * encap = x - > encap ;
2007-04-11 07:50:43 +04:00
struct udphdr * uh = ( void * ) ( skb_network_header ( skb ) + ihl ) ;
2006-02-28 00:00:40 +03:00
/*
* 1 ) if the NAT - T peer ' s IP or port changed then
* advertize the change to the keying daemon .
* This is an inbound SA , so just compare
* SRC ports .
*/
if ( iph - > saddr ! = x - > props . saddr . a4 | |
uh - > source ! = encap - > encap_sport ) {
xfrm_address_t ipaddr ;
ipaddr . a4 = iph - > saddr ;
km_new_mapping ( x , & ipaddr , uh - > source ) ;
2007-02-09 17:24:47 +03:00
2006-02-28 00:00:40 +03:00
/* XXX: perhaps add an extra
* policy check here , to see
* if we should allow or
* reject a packet from a
* different source
* address / port .
*/
2005-04-17 02:20:36 +04:00
}
2007-02-09 17:24:47 +03:00
2006-02-28 00:00:40 +03:00
/*
* 2 ) ignore UDP / TCP checksums in case
* of NAT - T in Transport Mode , or
* perform other post - processing fixes
* as per draft - ietf - ipsec - udp - encaps - 06 ,
* section 3.1 .2
*/
2006-10-04 10:47:05 +04:00
if ( x - > props . mode = = XFRM_MODE_TRANSPORT | |
x - > props . mode = = XFRM_MODE_BEET )
2006-02-28 00:00:40 +03:00
skb - > ip_summed = CHECKSUM_UNNECESSARY ;
2005-04-17 02:20:36 +04:00
}
2006-02-28 00:00:01 +03:00
iph - > protocol = nexthdr [ 1 ] ;
pskb_trim ( skb , skb - > len - alen - padlen - 2 ) ;
2007-03-13 19:51:52 +03:00
__skb_pull ( skb , sizeof ( * esph ) + esp - > conf . ivlen ) ;
skb_set_transport_header ( skb , - ihl ) ;
2006-02-28 00:00:01 +03:00
2005-04-17 02:20:36 +04:00
return 0 ;
out :
return - EINVAL ;
}
static u32 esp4_get_max_size ( struct xfrm_state * x , int mtu )
{
struct esp_data * esp = x - > data ;
2006-07-30 09:41:01 +04:00
u32 blksize = ALIGN ( crypto_blkcipher_blocksize ( esp - > conf . tfm ) , 4 ) ;
2006-10-04 10:47:05 +04:00
int enclen = 0 ;
switch ( x - > props . mode ) {
case XFRM_MODE_TUNNEL :
mtu = ALIGN ( mtu + 2 , blksize ) ;
break ;
default :
case XFRM_MODE_TRANSPORT :
/* The worst case */
2005-10-11 08:11:34 +04:00
mtu = ALIGN ( mtu + 2 , 4 ) + blksize - 4 ;
2006-10-04 10:47:05 +04:00
break ;
case XFRM_MODE_BEET :
2007-02-09 17:24:47 +03:00
/* The worst case. */
2006-10-04 10:47:05 +04:00
enclen = IPV4_BEET_PHMAXLEN ;
mtu = ALIGN ( mtu + enclen + 2 , blksize ) ;
break ;
2005-04-17 02:20:36 +04:00
}
2006-10-04 10:47:05 +04:00
2005-04-17 02:20:36 +04:00
if ( esp - > conf . padlen )
2005-10-11 08:11:08 +04:00
mtu = ALIGN ( mtu , esp - > conf . padlen ) ;
2005-04-17 02:20:36 +04:00
2006-10-04 10:47:05 +04:00
return mtu + x - > props . header_len + esp - > auth . icv_trunc_len - enclen ;
2005-04-17 02:20:36 +04:00
}
static void esp4_err ( struct sk_buff * skb , u32 info )
{
struct iphdr * iph = ( struct iphdr * ) skb - > data ;
struct ip_esp_hdr * esph = ( struct ip_esp_hdr * ) ( skb - > data + ( iph - > ihl < < 2 ) ) ;
struct xfrm_state * x ;
if ( skb - > h . icmph - > type ! = ICMP_DEST_UNREACH | |
skb - > h . icmph - > code ! = ICMP_FRAG_NEEDED )
return ;
x = xfrm_state_lookup ( ( xfrm_address_t * ) & iph - > daddr , esph - > spi , IPPROTO_ESP , AF_INET ) ;
if ( ! x )
return ;
2005-08-10 07:50:53 +04:00
NETDEBUG ( KERN_DEBUG " pmtu discovery on SA ESP/%08x/%08x \n " ,
ntohl ( esph - > spi ) , ntohl ( iph - > daddr ) ) ;
2005-04-17 02:20:36 +04:00
xfrm_state_put ( x ) ;
}
static void esp_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 esp_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
/* null auth and encryption can have zero length keys */
if ( x - > aalg ) {
if ( x - > aalg - > alg_key_len > 512 )
goto error ;
}
if ( x - > ealg = = NULL )
goto error ;
2006-07-22 01:51:30 +04: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
esp - > auth . key = x - > aalg - > alg_key ;
esp - > auth . key_len = ( x - > aalg - > alg_key_len + 7 ) / 8 ;
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 ;
if ( crypto_hash_setkey ( hash , esp - > auth . key , esp - > auth . key_len ) )
2005-04-17 02:20:36 +04:00
goto error ;
aalg_desc = xfrm_aalg_get_byname ( x - > aalg - > alg_name , 0 ) ;
BUG_ON ( ! aalg_desc ) ;
if ( aalg_desc - > uinfo . auth . icv_fullbits / 8 ! =
2006-08-20 08:24:50 +04:00
crypto_hash_digestsize ( hash ) ) {
2005-08-10 07:50:53 +04:00
NETDEBUG ( KERN_INFO " ESP: %s digestsize %u != %hu \n " ,
x - > aalg - > alg_name ,
2006-08-20 08:24:50 +04:00
crypto_hash_digestsize ( hash ) ,
2005-08-10 07:50:53 +04:00
aalg_desc - > uinfo . auth . icv_fullbits / 8 ) ;
2005-04-17 02:20:36 +04:00
goto error ;
}
esp - > auth . icv_full_len = aalg_desc - > uinfo . auth . icv_fullbits / 8 ;
esp - > auth . icv_trunc_len = aalg_desc - > uinfo . auth . icv_truncbits / 8 ;
esp - > auth . work_icv = kmalloc ( esp - > auth . icv_full_len , GFP_KERNEL ) ;
if ( ! esp - > auth . work_icv )
goto error ;
}
esp - > conf . key = x - > ealg - > alg_key ;
esp - > conf . key_len = ( x - > ealg - > alg_key_len + 7 ) / 8 ;
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
}
2006-07-30 09:41:01 +04:00
if ( crypto_blkcipher_setkey ( tfm , esp - > conf . key , esp - > conf . key_len ) )
2005-04-17 02:20:36 +04:00
goto error ;
x - > props . header_len = sizeof ( struct ip_esp_hdr ) + esp - > conf . ivlen ;
2006-09-23 02:05:15 +04:00
if ( x - > props . mode = = XFRM_MODE_TUNNEL )
2005-04-17 02:20:36 +04:00
x - > props . header_len + = sizeof ( struct iphdr ) ;
if ( x - > encap ) {
struct xfrm_encap_tmpl * encap = x - > encap ;
switch ( encap - > encap_type ) {
default :
goto error ;
case UDP_ENCAP_ESPINUDP :
x - > props . header_len + = sizeof ( struct udphdr ) ;
break ;
case UDP_ENCAP_ESPINUDP_NON_IKE :
x - > props . header_len + = sizeof ( struct udphdr ) + 2 * sizeof ( u32 ) ;
break ;
}
}
x - > data = esp ;
x - > props . trailer_len = esp4_get_max_size ( x , 0 ) - x - > props . header_len ;
return 0 ;
error :
x - > data = esp ;
esp_destroy ( x ) ;
x - > data = NULL ;
return - EINVAL ;
}
static struct xfrm_type esp_type =
{
. description = " ESP4 " ,
. owner = THIS_MODULE ,
. proto = IPPROTO_ESP ,
. init_state = esp_init_state ,
. destructor = esp_destroy ,
. get_max_size = esp4_get_max_size ,
. input = esp_input ,
. output = esp_output
} ;
static struct net_protocol esp4_protocol = {
. handler = xfrm4_rcv ,
. err_handler = esp4_err ,
. no_policy = 1 ,
} ;
static int __init esp4_init ( void )
{
if ( xfrm_register_type ( & esp_type , AF_INET ) < 0 ) {
printk ( KERN_INFO " ip esp init: can't add xfrm type \n " ) ;
return - EAGAIN ;
}
if ( inet_add_protocol ( & esp4_protocol , IPPROTO_ESP ) < 0 ) {
printk ( KERN_INFO " ip esp init: can't add protocol \n " ) ;
xfrm_unregister_type ( & esp_type , AF_INET ) ;
return - EAGAIN ;
}
return 0 ;
}
static void __exit esp4_fini ( void )
{
if ( inet_del_protocol ( & esp4_protocol , IPPROTO_ESP ) < 0 )
printk ( KERN_INFO " ip esp close: can't remove protocol \n " ) ;
if ( xfrm_unregister_type ( & esp_type , AF_INET ) < 0 )
printk ( KERN_INFO " ip esp close: can't remove xfrm type \n " ) ;
}
module_init ( esp4_init ) ;
module_exit ( esp4_fini ) ;
MODULE_LICENSE ( " GPL " ) ;