2005-04-16 15:20:36 -07:00
/*
* Copyright ( C ) 2002 USAGI / WIDE Project
2007-02-09 23:24:49 +09:00
*
2005-04-16 15:20:36 -07: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 23:24:49 +09:00
*
2005-04-16 15:20:36 -07: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 23:24:49 +09:00
*
2005-04-16 15:20:36 -07: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 23:24:49 +09:00
* Mitsuru KANDA @ USAGI : IPv6 Support
2005-04-16 15:20:36 -07:00
* Kazunori MIYAZAWA @ USAGI :
* Kunihiro Ishiguro < kunihiro @ ipinfusion . com >
2007-02-09 23:24:49 +09:00
*
2005-04-16 15:20:36 -07:00
* This file is derived from net / ipv4 / ah . c .
*/
2009-10-07 22:49:01 +00:00
# include <crypto/hash.h>
2005-04-16 15:20:36 -07:00
# include <linux/module.h>
# include <net/ip.h>
# include <net/ah.h>
# include <linux/crypto.h>
# include <linux/pfkeyv2.h>
# include <linux/string.h>
2009-10-07 22:49:01 +00:00
# include <linux/scatterlist.h>
2005-04-16 15:20:36 -07:00
# include <net/icmp.h>
# include <net/ipv6.h>
2005-12-27 02:43:12 -02:00
# include <net/protocol.h>
2005-04-16 15:20:36 -07:00
# include <net/xfrm.h>
2009-10-07 22:49:01 +00:00
# define IPV6HDR_BASELEN 8
struct tmp_ext {
# if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
struct in6_addr saddr ;
# endif
struct in6_addr daddr ;
char hdrs [ 0 ] ;
} ;
struct ah_skb_cb {
struct xfrm_skb_cb xfrm ;
void * tmp ;
} ;
# define AH_SKB_CB(__skb) ((struct ah_skb_cb *)&((__skb)->cb[0]))
static void * ah_alloc_tmp ( struct crypto_ahash * ahash , int nfrags ,
unsigned int size )
{
unsigned int len ;
len = size + crypto_ahash_digestsize ( ahash ) +
( crypto_ahash_alignmask ( ahash ) &
~ ( crypto_tfm_ctx_alignment ( ) - 1 ) ) ;
len = ALIGN ( len , crypto_tfm_ctx_alignment ( ) ) ;
len + = sizeof ( struct ahash_request ) + crypto_ahash_reqsize ( ahash ) ;
len = ALIGN ( len , __alignof__ ( struct scatterlist ) ) ;
len + = sizeof ( struct scatterlist ) * nfrags ;
return kmalloc ( len , GFP_ATOMIC ) ;
}
static inline struct tmp_ext * ah_tmp_ext ( void * base )
{
return base + IPV6HDR_BASELEN ;
}
static inline u8 * ah_tmp_auth ( u8 * tmp , unsigned int offset )
{
return tmp + offset ;
}
static inline u8 * ah_tmp_icv ( struct crypto_ahash * ahash , void * tmp ,
unsigned int offset )
{
return PTR_ALIGN ( ( u8 * ) tmp + offset , crypto_ahash_alignmask ( ahash ) + 1 ) ;
}
static inline struct ahash_request * ah_tmp_req ( struct crypto_ahash * ahash ,
u8 * icv )
{
struct ahash_request * req ;
req = ( void * ) PTR_ALIGN ( icv + crypto_ahash_digestsize ( ahash ) ,
crypto_tfm_ctx_alignment ( ) ) ;
ahash_request_set_tfm ( req , ahash ) ;
return req ;
}
static inline struct scatterlist * ah_req_sg ( struct crypto_ahash * ahash ,
struct ahash_request * req )
{
return ( void * ) ALIGN ( ( unsigned long ) ( req + 1 ) +
crypto_ahash_reqsize ( ahash ) ,
__alignof__ ( struct scatterlist ) ) ;
}
2005-04-16 15:20:36 -07:00
static int zero_out_mutable_opts ( struct ipv6_opt_hdr * opthdr )
{
u8 * opt = ( u8 * ) opthdr ;
int len = ipv6_optlen ( opthdr ) ;
int off = 0 ;
int optlen = 0 ;
off + = 2 ;
len - = 2 ;
while ( len > 0 ) {
switch ( opt [ off ] ) {
case IPV6_TLV_PAD0 :
optlen = 1 ;
break ;
default :
2007-02-09 23:24:49 +09:00
if ( len < 2 )
2005-04-16 15:20:36 -07:00
goto bad ;
optlen = opt [ off + 1 ] + 2 ;
if ( len < optlen )
goto bad ;
if ( opt [ off ] & 0x20 )
memset ( & opt [ off + 2 ] , 0 , opt [ off + 1 ] ) ;
break ;
}
off + = optlen ;
len - = optlen ;
}
if ( len = = 0 )
return 1 ;
bad :
return 0 ;
}
2007-06-26 23:56:32 -07:00
# if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
2006-08-23 19:29:47 -07:00
/**
* ipv6_rearrange_destopt - rearrange IPv6 destination options header
* @ iph : IPv6 header
* @ destopt : destionation options header
*/
static void ipv6_rearrange_destopt ( struct ipv6hdr * iph , struct ipv6_opt_hdr * destopt )
{
u8 * opt = ( u8 * ) destopt ;
int len = ipv6_optlen ( destopt ) ;
int off = 0 ;
int optlen = 0 ;
off + = 2 ;
len - = 2 ;
while ( len > 0 ) {
switch ( opt [ off ] ) {
case IPV6_TLV_PAD0 :
optlen = 1 ;
break ;
default :
if ( len < 2 )
goto bad ;
optlen = opt [ off + 1 ] + 2 ;
if ( len < optlen )
goto bad ;
/* Rearrange the source address in @iph and the
* addresses in home address option for final source .
* See 11.3 .2 of RFC 3775 for details .
*/
if ( opt [ off ] = = IPV6_TLV_HAO ) {
struct in6_addr final_addr ;
struct ipv6_destopt_hao * hao ;
hao = ( struct ipv6_destopt_hao * ) & opt [ off ] ;
if ( hao - > length ! = sizeof ( hao - > addr ) ) {
if ( net_ratelimit ( ) )
printk ( KERN_WARNING " destopt hao: invalid header length: %u \n " , hao - > length ) ;
goto bad ;
}
ipv6_addr_copy ( & final_addr , & hao - > addr ) ;
ipv6_addr_copy ( & hao - > addr , & iph - > saddr ) ;
ipv6_addr_copy ( & iph - > saddr , & final_addr ) ;
}
break ;
}
off + = optlen ;
len - = optlen ;
}
2006-08-24 23:18:12 +09:00
/* Note: ok if len == 0 */
2006-08-23 19:29:47 -07:00
bad :
return ;
}
2007-06-26 23:51:41 -07:00
# else
static void ipv6_rearrange_destopt ( struct ipv6hdr * iph , struct ipv6_opt_hdr * destopt ) { }
2006-08-23 19:29:47 -07:00
# endif
2005-04-16 15:20:36 -07:00
/**
* ipv6_rearrange_rthdr - rearrange IPv6 routing header
* @ iph : IPv6 header
* @ rthdr : routing header
*
* Rearrange the destination address in @ iph and the addresses in @ rthdr
* so that they appear in the order they will at the final destination .
* See Appendix A2 of RFC 2402 for details .
*/
static void ipv6_rearrange_rthdr ( struct ipv6hdr * iph , struct ipv6_rt_hdr * rthdr )
{
int segments , segments_left ;
struct in6_addr * addrs ;
struct in6_addr final_addr ;
segments_left = rthdr - > segments_left ;
if ( segments_left = = 0 )
return ;
2007-02-09 23:24:49 +09:00
rthdr - > segments_left = 0 ;
2005-04-16 15:20:36 -07:00
/* The value of rthdr->hdrlen has been verified either by the system
* call if it is locally generated , or by ipv6_rthdr_rcv ( ) for incoming
* packets . So we can assume that it is even and that segments is
* greater than or equal to segments_left .
*
* For the same reason we can assume that this option is of type 0.
*/
segments = rthdr - > hdrlen > > 1 ;
addrs = ( ( struct rt0_hdr * ) rthdr ) - > addr ;
ipv6_addr_copy ( & final_addr , addrs + segments - 1 ) ;
addrs + = segments - segments_left ;
memmove ( addrs + 1 , addrs , ( segments_left - 1 ) * sizeof ( * addrs ) ) ;
ipv6_addr_copy ( addrs , & iph - > daddr ) ;
ipv6_addr_copy ( & iph - > daddr , & final_addr ) ;
}
2006-08-23 19:29:47 -07:00
static int ipv6_clear_mutable_options ( struct ipv6hdr * iph , int len , int dir )
2005-04-16 15:20:36 -07:00
{
union {
struct ipv6hdr * iph ;
struct ipv6_opt_hdr * opth ;
struct ipv6_rt_hdr * rth ;
char * raw ;
} exthdr = { . iph = iph } ;
char * end = exthdr . raw + len ;
int nexthdr = iph - > nexthdr ;
exthdr . iph + + ;
while ( exthdr . raw < end ) {
switch ( nexthdr ) {
2006-08-23 19:29:47 -07:00
case NEXTHDR_DEST :
if ( dir = = XFRM_POLICY_OUT )
ipv6_rearrange_destopt ( iph , exthdr . opth ) ;
2005-04-16 15:20:36 -07:00
case NEXTHDR_HOP :
if ( ! zero_out_mutable_opts ( exthdr . opth ) ) {
2005-08-09 20:50:53 -07:00
LIMIT_NETDEBUG (
2005-04-16 15:20:36 -07:00
KERN_WARNING " overrun %sopts \n " ,
nexthdr = = NEXTHDR_HOP ?
2005-08-09 20:50:53 -07:00
" hop " : " dest " ) ;
2005-04-16 15:20:36 -07:00
return - EINVAL ;
}
break ;
case NEXTHDR_ROUTING :
ipv6_rearrange_rthdr ( iph , exthdr . rth ) ;
break ;
default :
return 0 ;
}
nexthdr = exthdr . opth - > nexthdr ;
exthdr . raw + = ipv6_optlen ( exthdr . opth ) ;
}
return 0 ;
}
2009-10-07 22:49:01 +00:00
static void ah6_output_done ( struct crypto_async_request * base , int err )
{
int extlen ;
u8 * iph_base ;
u8 * icv ;
struct sk_buff * skb = base - > data ;
struct xfrm_state * x = skb_dst ( skb ) - > xfrm ;
struct ah_data * ahp = x - > data ;
struct ipv6hdr * top_iph = ipv6_hdr ( skb ) ;
struct ip_auth_hdr * ah = ip_auth_hdr ( skb ) ;
struct tmp_ext * iph_ext ;
extlen = skb_network_header_len ( skb ) - sizeof ( struct ipv6hdr ) ;
if ( extlen )
extlen + = sizeof ( * iph_ext ) ;
iph_base = AH_SKB_CB ( skb ) - > tmp ;
iph_ext = ah_tmp_ext ( iph_base ) ;
icv = ah_tmp_icv ( ahp - > ahash , iph_ext , extlen ) ;
memcpy ( ah - > auth_data , icv , ahp - > icv_trunc_len ) ;
memcpy ( top_iph , iph_base , IPV6HDR_BASELEN ) ;
if ( extlen ) {
# if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
memcpy ( & top_iph - > saddr , iph_ext , extlen ) ;
# else
memcpy ( & top_iph - > daddr , iph_ext , extlen ) ;
# endif
}
err = ah - > nexthdr ;
kfree ( AH_SKB_CB ( skb ) - > tmp ) ;
xfrm_output_resume ( skb , err ) ;
}
2005-04-16 15:20:36 -07:00
static int ah6_output ( struct xfrm_state * x , struct sk_buff * skb )
{
int err ;
2009-10-07 22:49:01 +00:00
int nfrags ;
2005-04-16 15:20:36 -07:00
int extlen ;
2009-10-07 22:49:01 +00:00
u8 * iph_base ;
u8 * icv ;
u8 nexthdr ;
struct sk_buff * trailer ;
struct crypto_ahash * ahash ;
struct ahash_request * req ;
struct scatterlist * sg ;
2005-04-16 15:20:36 -07:00
struct ipv6hdr * top_iph ;
struct ip_auth_hdr * ah ;
struct ah_data * ahp ;
2009-10-07 22:49:01 +00:00
struct tmp_ext * iph_ext ;
ahp = x - > data ;
ahash = ahp - > ahash ;
if ( ( err = skb_cow_data ( skb , 0 , & trailer ) ) < 0 )
goto out ;
nfrags = err ;
2005-04-16 15:20:36 -07:00
2007-10-10 15:44:06 -07:00
skb_push ( skb , - skb_network_offset ( skb ) ) ;
2009-10-07 22:49:01 +00:00
extlen = skb_network_header_len ( skb ) - sizeof ( struct ipv6hdr ) ;
if ( extlen )
extlen + = sizeof ( * iph_ext ) ;
err = - ENOMEM ;
iph_base = ah_alloc_tmp ( ahash , nfrags , IPV6HDR_BASELEN + extlen ) ;
if ( ! iph_base )
goto out ;
iph_ext = ah_tmp_ext ( iph_base ) ;
icv = ah_tmp_icv ( ahash , iph_ext , extlen ) ;
req = ah_tmp_req ( ahash , icv ) ;
sg = ah_req_sg ( ahash , req ) ;
ah = ip_auth_hdr ( skb ) ;
memset ( ah - > auth_data , 0 , ahp - > icv_trunc_len ) ;
2007-10-09 13:25:59 -07:00
top_iph = ipv6_hdr ( skb ) ;
2005-04-16 15:20:36 -07:00
top_iph - > payload_len = htons ( skb - > len - sizeof ( * top_iph ) ) ;
2007-10-09 13:25:59 -07:00
nexthdr = * skb_mac_header ( skb ) ;
* skb_mac_header ( skb ) = IPPROTO_AH ;
2005-04-16 15:20:36 -07:00
/* When there are no extension headers, we only need to save the first
* 8 bytes of the base IP header .
*/
2009-10-07 22:49:01 +00:00
memcpy ( iph_base , top_iph , IPV6HDR_BASELEN ) ;
2005-04-16 15:20:36 -07:00
if ( extlen ) {
2007-06-26 23:56:32 -07:00
# if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
2009-10-07 22:49:01 +00:00
memcpy ( iph_ext , & top_iph - > saddr , extlen ) ;
2006-08-23 19:29:47 -07:00
# else
2009-10-07 22:49:01 +00:00
memcpy ( iph_ext , & top_iph - > daddr , extlen ) ;
2006-08-24 23:18:12 +09:00
# endif
2005-04-16 15:20:36 -07:00
err = ipv6_clear_mutable_options ( top_iph ,
2009-10-07 22:49:01 +00:00
extlen - sizeof ( * iph_ext ) +
2006-08-24 23:18:12 +09:00
sizeof ( * top_iph ) ,
XFRM_POLICY_OUT ) ;
2005-04-16 15:20:36 -07:00
if ( err )
2009-10-07 22:49:01 +00:00
goto out_free ;
2005-04-16 15:20:36 -07:00
}
ah - > nexthdr = nexthdr ;
top_iph - > priority = 0 ;
top_iph - > flow_lbl [ 0 ] = 0 ;
top_iph - > flow_lbl [ 1 ] = 0 ;
top_iph - > flow_lbl [ 2 ] = 0 ;
top_iph - > hop_limit = 0 ;
2007-10-10 15:45:25 -07:00
ah - > hdrlen = ( XFRM_ALIGN8 ( sizeof ( * ah ) + ahp - > icv_trunc_len ) > > 2 ) - 2 ;
2005-04-16 15:20:36 -07:00
ah - > reserved = 0 ;
ah - > spi = x - > id . spi ;
2008-02-12 22:50:35 -08:00
ah - > seq_no = htonl ( XFRM_SKB_CB ( skb ) - > seq . output ) ;
2007-10-09 13:33:35 -07:00
2009-10-07 22:49:01 +00:00
sg_init_table ( sg , nfrags ) ;
skb_to_sgvec ( skb , sg , 0 , skb - > len ) ;
2005-04-16 15:20:36 -07:00
2009-10-07 22:49:01 +00:00
ahash_request_set_crypt ( req , sg , icv , skb - > len ) ;
ahash_request_set_callback ( req , 0 , ah6_output_done , skb ) ;
AH_SKB_CB ( skb ) - > tmp = iph_base ;
2005-04-16 15:20:36 -07:00
2009-10-07 22:49:01 +00:00
err = crypto_ahash_digest ( req ) ;
if ( err ) {
if ( err = = - EINPROGRESS )
goto out ;
if ( err = = - EBUSY )
err = NET_XMIT_DROP ;
goto out_free ;
}
memcpy ( ah - > auth_data , icv , ahp - > icv_trunc_len ) ;
memcpy ( top_iph , iph_base , IPV6HDR_BASELEN ) ;
if ( extlen ) {
2007-06-26 23:56:32 -07:00
# if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
2009-10-07 22:49:01 +00:00
memcpy ( & top_iph - > saddr , iph_ext , extlen ) ;
2006-08-23 19:29:47 -07:00
# else
2009-10-07 22:49:01 +00:00
memcpy ( & top_iph - > daddr , iph_ext , extlen ) ;
2006-08-23 19:29:47 -07:00
# endif
2005-04-16 15:20:36 -07:00
}
2009-10-07 22:49:01 +00:00
out_free :
kfree ( iph_base ) ;
out :
2005-04-16 15:20:36 -07:00
return err ;
}
2009-10-07 22:49:01 +00:00
static void ah6_input_done ( struct crypto_async_request * base , int err )
{
u8 * auth_data ;
u8 * icv ;
u8 * work_iph ;
struct sk_buff * skb = base - > data ;
struct xfrm_state * x = xfrm_input_state ( skb ) ;
struct ah_data * ahp = x - > data ;
struct ip_auth_hdr * ah = ip_auth_hdr ( skb ) ;
int hdr_len = skb_network_header_len ( skb ) ;
int ah_hlen = ( ah - > hdrlen + 2 ) < < 2 ;
work_iph = AH_SKB_CB ( skb ) - > tmp ;
auth_data = ah_tmp_auth ( work_iph , hdr_len ) ;
icv = ah_tmp_icv ( ahp - > ahash , auth_data , ahp - > icv_trunc_len ) ;
err = memcmp ( icv , auth_data , ahp - > icv_trunc_len ) ? - EBADMSG : 0 ;
if ( err )
goto out ;
skb - > network_header + = ah_hlen ;
memcpy ( skb_network_header ( skb ) , work_iph , hdr_len ) ;
__skb_pull ( skb , ah_hlen + hdr_len ) ;
skb_set_transport_header ( skb , - hdr_len ) ;
err = ah - > nexthdr ;
out :
kfree ( AH_SKB_CB ( skb ) - > tmp ) ;
xfrm_input_resume ( skb , err ) ;
}
2006-04-01 00:52:46 -08:00
static int ah6_input ( struct xfrm_state * x , struct sk_buff * skb )
2005-04-16 15:20:36 -07:00
{
/*
* Before process AH
* [ IPv6 ] [ Ext1 ] [ Ext2 ] [ AH ] [ Dest ] [ Payload ]
* | < - - - - - - - - - - - - - - > | hdr_len
*
* To erase AH :
* Keeping copy of cleared headers . After AH processing ,
2007-04-10 21:21:55 -07:00
* Moving the pointer of skb - > network_header by using skb_pull as long
* as AH header length . Then copy back the copy as long as hdr_len
2005-04-16 15:20:36 -07:00
* If destination header following AH exists , copy it into after [ Ext2 ] .
2007-02-09 23:24:49 +09:00
*
2005-04-16 15:20:36 -07:00
* | < > | [ IPv6 ] [ Ext1 ] [ Ext2 ] [ Dest ] [ Payload ]
* There is offset of AH before IPv6 header after the process .
*/
2009-10-07 22:49:01 +00:00
u8 * auth_data ;
u8 * icv ;
u8 * work_iph ;
struct sk_buff * trailer ;
struct crypto_ahash * ahash ;
struct ahash_request * req ;
struct scatterlist * sg ;
2007-10-10 15:45:25 -07:00
struct ip_auth_hdr * ah ;
2007-04-25 17:54:47 -07:00
struct ipv6hdr * ip6h ;
2005-04-16 15:20:36 -07:00
struct ah_data * ahp ;
u16 hdr_len ;
u16 ah_hlen ;
int nexthdr ;
2009-10-07 22:49:01 +00:00
int nfrags ;
int err = - ENOMEM ;
2005-04-16 15:20:36 -07:00
if ( ! pskb_may_pull ( skb , sizeof ( struct ip_auth_hdr ) ) )
goto out ;
/* We are going to _remove_ AH header to keep sockets happy,
* so . . . Later this can change . */
if ( skb_cloned ( skb ) & &
pskb_expand_head ( skb , 0 , 0 , GFP_ATOMIC ) )
goto out ;
2007-10-17 21:30:07 -07:00
skb - > ip_summed = CHECKSUM_NONE ;
2009-10-07 22:49:01 +00:00
hdr_len = skb_network_header_len ( skb ) ;
2007-10-10 15:45:25 -07:00
ah = ( struct ip_auth_hdr * ) skb - > data ;
2005-04-16 15:20:36 -07:00
ahp = x - > data ;
2009-10-07 22:49:01 +00:00
ahash = ahp - > ahash ;
2005-04-16 15:20:36 -07:00
nexthdr = ah - > nexthdr ;
ah_hlen = ( ah - > hdrlen + 2 ) < < 2 ;
2007-10-10 15:45:25 -07:00
if ( ah_hlen ! = XFRM_ALIGN8 ( sizeof ( * ah ) + ahp - > icv_full_len ) & &
ah_hlen ! = XFRM_ALIGN8 ( sizeof ( * ah ) + ahp - > icv_trunc_len ) )
2007-02-09 23:24:49 +09:00
goto out ;
2005-04-16 15:20:36 -07:00
if ( ! pskb_may_pull ( skb , ah_hlen ) )
goto out ;
2007-04-25 17:54:47 -07:00
ip6h = ipv6_hdr ( skb ) ;
2009-10-07 22:49:01 +00:00
skb_push ( skb , hdr_len ) ;
if ( ( err = skb_cow_data ( skb , 0 , & trailer ) ) < 0 )
goto out ;
nfrags = err ;
work_iph = ah_alloc_tmp ( ahash , nfrags , hdr_len + ahp - > icv_trunc_len ) ;
if ( ! work_iph )
goto out ;
auth_data = ah_tmp_auth ( work_iph , hdr_len ) ;
icv = ah_tmp_icv ( ahash , auth_data , ahp - > icv_trunc_len ) ;
req = ah_tmp_req ( ahash , icv ) ;
sg = ah_req_sg ( ahash , req ) ;
memcpy ( work_iph , ip6h , hdr_len ) ;
memcpy ( auth_data , ah - > auth_data , ahp - > icv_trunc_len ) ;
memset ( ah - > auth_data , 0 , ahp - > icv_trunc_len ) ;
2007-04-25 17:54:47 -07:00
if ( ipv6_clear_mutable_options ( ip6h , hdr_len , XFRM_POLICY_IN ) )
2009-10-07 22:49:01 +00:00
goto out_free ;
2007-04-25 17:54:47 -07:00
ip6h - > priority = 0 ;
ip6h - > flow_lbl [ 0 ] = 0 ;
ip6h - > flow_lbl [ 1 ] = 0 ;
ip6h - > flow_lbl [ 2 ] = 0 ;
ip6h - > hop_limit = 0 ;
2005-04-16 15:20:36 -07:00
2009-10-07 22:49:01 +00:00
sg_init_table ( sg , nfrags ) ;
skb_to_sgvec ( skb , sg , 0 , skb - > len ) ;
2005-04-16 15:20:36 -07:00
2009-10-07 22:49:01 +00:00
ahash_request_set_crypt ( req , sg , icv , skb - > len ) ;
ahash_request_set_callback ( req , 0 , ah6_input_done , skb ) ;
AH_SKB_CB ( skb ) - > tmp = work_iph ;
err = crypto_ahash_digest ( req ) ;
if ( err ) {
if ( err = = - EINPROGRESS )
goto out ;
if ( err = = - EBUSY )
err = NET_XMIT_DROP ;
goto out_free ;
2005-04-16 15:20:36 -07:00
}
2007-11-13 21:45:58 -08:00
2009-10-07 22:49:01 +00:00
err = memcmp ( icv , auth_data , ahp - > icv_trunc_len ) ? - EBADMSG : 0 ;
2007-11-13 21:45:58 -08:00
if ( err )
2009-10-07 22:49:01 +00:00
goto out_free ;
2005-04-16 15:20:36 -07:00
2007-04-10 21:21:55 -07:00
skb - > network_header + = ah_hlen ;
2009-10-07 22:49:01 +00:00
memcpy ( skb_network_header ( skb ) , work_iph , hdr_len ) ;
2007-04-10 21:21:55 -07:00
skb - > transport_header = skb - > network_header ;
2006-05-27 23:06:13 -07:00
__skb_pull ( skb , ah_hlen + hdr_len ) ;
2005-04-16 15:20:36 -07:00
2009-10-07 22:49:01 +00:00
err = nexthdr ;
2005-04-16 15:20:36 -07:00
2009-10-07 22:49:01 +00:00
out_free :
kfree ( work_iph ) ;
2005-04-16 15:20:36 -07:00
out :
2006-08-20 14:24:50 +10:00
return err ;
2005-04-16 15:20:36 -07:00
}
2007-02-09 23:24:49 +09:00
static void ah6_err ( struct sk_buff * skb , struct inet6_skb_parm * opt ,
2009-06-23 04:31:07 -07:00
u8 type , u8 code , int offset , __be32 info )
2005-04-16 15:20:36 -07:00
{
2008-11-25 17:59:27 -08:00
struct net * net = dev_net ( skb - > dev ) ;
2005-04-16 15:20:36 -07:00
struct ipv6hdr * iph = ( struct ipv6hdr * ) skb - > data ;
struct ip_auth_hdr * ah = ( struct ip_auth_hdr * ) ( skb - > data + offset ) ;
struct xfrm_state * x ;
if ( type ! = ICMPV6_DEST_UNREACH & &
type ! = ICMPV6_PKT_TOOBIG )
return ;
2010-02-22 16:20:22 -08:00
x = xfrm_state_lookup ( net , skb - > mark , ( xfrm_address_t * ) & iph - > daddr , ah - > spi , IPPROTO_AH , AF_INET6 ) ;
2005-04-16 15:20:36 -07:00
if ( ! x )
return ;
2008-10-29 12:52:50 -07:00
NETDEBUG ( KERN_DEBUG " pmtu discovery on SA AH/%08x/%pI6 \n " ,
2008-10-28 16:09:23 -07:00
ntohl ( ah - > spi ) , & iph - > daddr ) ;
2005-04-16 15:20:36 -07:00
xfrm_state_put ( x ) ;
}
2005-06-20 13:18:08 -07:00
static int ah6_init_state ( struct xfrm_state * x )
2005-04-16 15:20:36 -07:00
{
struct ah_data * ahp = NULL ;
struct xfrm_algo_desc * aalg_desc ;
2009-10-07 22:49:01 +00:00
struct crypto_ahash * ahash ;
2005-04-16 15:20:36 -07:00
if ( ! x - > aalg )
goto error ;
if ( x - > encap )
goto error ;
2006-03-20 23:01:32 -08:00
ahp = kzalloc ( sizeof ( * ahp ) , GFP_KERNEL ) ;
2005-04-16 15:20:36 -07:00
if ( ahp = = NULL )
return - ENOMEM ;
2009-10-07 22:49:01 +00:00
ahash = crypto_alloc_ahash ( x - > aalg - > alg_name , 0 , 0 ) ;
if ( IS_ERR ( ahash ) )
2006-08-20 14:24:50 +10:00
goto error ;
2009-10-07 22:49:01 +00:00
ahp - > ahash = ahash ;
if ( crypto_ahash_setkey ( ahash , x - > aalg - > alg_key ,
2007-10-08 17:14:34 -07:00
( x - > aalg - > alg_key_len + 7 ) / 8 ) )
2005-04-16 15:20:36 -07:00
goto error ;
2007-02-09 23:24:49 +09:00
2005-04-16 15:20:36 -07:00
/*
* Lookup the algorithm description maintained by xfrm_algo ,
* verify crypto transform properties , and store information
* we need for AH processing . This lookup cannot fail here
2006-08-20 14:24:50 +10:00
* after a successful crypto_alloc_hash ( ) .
2005-04-16 15:20:36 -07:00
*/
aalg_desc = xfrm_aalg_get_byname ( x - > aalg - > alg_name , 0 ) ;
BUG_ON ( ! aalg_desc ) ;
if ( aalg_desc - > uinfo . auth . icv_fullbits / 8 ! =
2009-10-07 22:49:01 +00:00
crypto_ahash_digestsize ( ahash ) ) {
2005-04-16 15:20:36 -07:00
printk ( KERN_INFO " AH: %s digestsize %u != %hu \n " ,
2009-10-07 22:49:01 +00:00
x - > aalg - > alg_name , crypto_ahash_digestsize ( ahash ) ,
2005-04-16 15:20:36 -07:00
aalg_desc - > uinfo . auth . icv_fullbits / 8 ) ;
goto error ;
}
2007-02-09 23:24:49 +09:00
2005-04-16 15:20:36 -07:00
ahp - > icv_full_len = aalg_desc - > uinfo . auth . icv_fullbits / 8 ;
2009-11-25 00:29:53 +00:00
ahp - > icv_trunc_len = x - > aalg - > alg_trunc_len / 8 ;
2007-02-09 23:24:49 +09:00
2005-04-16 15:20:36 -07:00
BUG_ON ( ahp - > icv_trunc_len > MAX_AH_AUTH_LEN ) ;
2007-02-09 23:24:49 +09:00
2007-10-10 15:45:25 -07:00
x - > props . header_len = XFRM_ALIGN8 ( sizeof ( struct ip_auth_hdr ) +
ahp - > icv_trunc_len ) ;
2007-10-17 21:35:15 -07:00
switch ( x - > props . mode ) {
case XFRM_MODE_BEET :
case XFRM_MODE_TRANSPORT :
break ;
case XFRM_MODE_TUNNEL :
2005-04-16 15:20:36 -07:00
x - > props . header_len + = sizeof ( struct ipv6hdr ) ;
2007-10-22 02:30:15 -07:00
break ;
2007-10-17 21:35:15 -07:00
default :
goto error ;
}
2005-04-16 15:20:36 -07:00
x - > data = ahp ;
return 0 ;
error :
if ( ahp ) {
2009-10-07 22:49:01 +00:00
crypto_free_ahash ( ahp - > ahash ) ;
2005-04-16 15:20:36 -07:00
kfree ( ahp ) ;
}
return - EINVAL ;
}
static void ah6_destroy ( struct xfrm_state * x )
{
struct ah_data * ahp = x - > data ;
if ( ! ahp )
return ;
2009-10-07 22:49:01 +00:00
crypto_free_ahash ( ahp - > ahash ) ;
2005-04-16 15:20:36 -07:00
kfree ( ahp ) ;
}
2008-01-30 19:11:50 -08:00
static const struct xfrm_type ah6_type =
2005-04-16 15:20:36 -07:00
{
. description = " AH6 " ,
. owner = THIS_MODULE ,
. proto = IPPROTO_AH ,
2007-10-08 17:25:53 -07:00
. flags = XFRM_TYPE_REPLAY_PROT ,
2005-04-16 15:20:36 -07:00
. init_state = ah6_init_state ,
. destructor = ah6_destroy ,
. input = ah6_input ,
2006-08-23 17:57:28 -07:00
. output = ah6_output ,
. hdr_offset = xfrm6_find_1stfragopt ,
2005-04-16 15:20:36 -07:00
} ;
2009-09-14 12:22:28 +00:00
static const struct inet6_protocol ah6_protocol = {
2005-04-16 15:20:36 -07:00
. handler = xfrm6_rcv ,
. err_handler = ah6_err ,
. flags = INET6_PROTO_NOPOLICY ,
} ;
static int __init ah6_init ( void )
{
if ( xfrm_register_type ( & ah6_type , AF_INET6 ) < 0 ) {
printk ( KERN_INFO " ipv6 ah init: can't add xfrm type \n " ) ;
return - EAGAIN ;
}
if ( inet6_add_protocol ( & ah6_protocol , IPPROTO_AH ) < 0 ) {
printk ( KERN_INFO " ipv6 ah init: can't add protocol \n " ) ;
xfrm_unregister_type ( & ah6_type , AF_INET6 ) ;
return - EAGAIN ;
}
return 0 ;
}
static void __exit ah6_fini ( void )
{
if ( inet6_del_protocol ( & ah6_protocol , IPPROTO_AH ) < 0 )
printk ( KERN_INFO " ipv6 ah close: can't remove protocol \n " ) ;
if ( xfrm_unregister_type ( & ah6_type , AF_INET6 ) < 0 )
printk ( KERN_INFO " ipv6 ah close: can't remove xfrm type \n " ) ;
}
module_init ( ah6_init ) ;
module_exit ( ah6_fini ) ;
MODULE_LICENSE ( " GPL " ) ;
2007-06-26 23:57:49 -07:00
MODULE_ALIAS_XFRM_TYPE ( AF_INET6 , XFRM_PROTO_AH ) ;