2005-04-17 02:20:36 +04:00
/*
* xfrm_input . c
*
* Changes :
* YOSHIFUJI Hideaki @ USAGI
* Split up af - specific portion
2007-02-09 17:25:29 +03:00
*
2005-04-17 02:20:36 +04:00
*/
# include <linux/slab.h>
# include <linux/module.h>
2007-11-14 08:44:23 +03:00
# include <linux/netdevice.h>
# include <net/dst.h>
2005-04-17 02:20:36 +04:00
# include <net/ip.h>
# include <net/xfrm.h>
2006-12-07 07:33:20 +03:00
static struct kmem_cache * secpath_cachep __read_mostly ;
2005-04-17 02:20:36 +04:00
2014-03-14 10:28:07 +04:00
static DEFINE_SPINLOCK ( xfrm_input_afinfo_lock ) ;
static struct xfrm_input_afinfo __rcu * xfrm_input_afinfo [ NPROTO ] ;
int xfrm_input_register_afinfo ( struct xfrm_input_afinfo * afinfo )
{
int err = 0 ;
if ( unlikely ( afinfo = = NULL ) )
return - EINVAL ;
if ( unlikely ( afinfo - > family > = NPROTO ) )
return - EAFNOSUPPORT ;
spin_lock_bh ( & xfrm_input_afinfo_lock ) ;
if ( unlikely ( xfrm_input_afinfo [ afinfo - > family ] ! = NULL ) )
err = - ENOBUFS ;
else
rcu_assign_pointer ( xfrm_input_afinfo [ afinfo - > family ] , afinfo ) ;
spin_unlock_bh ( & xfrm_input_afinfo_lock ) ;
return err ;
}
EXPORT_SYMBOL ( xfrm_input_register_afinfo ) ;
int xfrm_input_unregister_afinfo ( struct xfrm_input_afinfo * afinfo )
{
int err = 0 ;
if ( unlikely ( afinfo = = NULL ) )
return - EINVAL ;
if ( unlikely ( afinfo - > family > = NPROTO ) )
return - EAFNOSUPPORT ;
spin_lock_bh ( & xfrm_input_afinfo_lock ) ;
if ( likely ( xfrm_input_afinfo [ afinfo - > family ] ! = NULL ) ) {
if ( unlikely ( xfrm_input_afinfo [ afinfo - > family ] ! = afinfo ) )
err = - EINVAL ;
else
RCU_INIT_POINTER ( xfrm_input_afinfo [ afinfo - > family ] , NULL ) ;
}
spin_unlock_bh ( & xfrm_input_afinfo_lock ) ;
synchronize_rcu ( ) ;
return err ;
}
EXPORT_SYMBOL ( xfrm_input_unregister_afinfo ) ;
static struct xfrm_input_afinfo * xfrm_input_get_afinfo ( unsigned int family )
{
struct xfrm_input_afinfo * afinfo ;
if ( unlikely ( family > = NPROTO ) )
return NULL ;
rcu_read_lock ( ) ;
afinfo = rcu_dereference ( xfrm_input_afinfo [ family ] ) ;
if ( unlikely ( ! afinfo ) )
rcu_read_unlock ( ) ;
return afinfo ;
}
static void xfrm_input_put_afinfo ( struct xfrm_input_afinfo * afinfo )
{
rcu_read_unlock ( ) ;
}
static int xfrm_rcv_cb ( struct sk_buff * skb , unsigned int family , u8 protocol ,
int err )
{
int ret ;
struct xfrm_input_afinfo * afinfo = xfrm_input_get_afinfo ( family ) ;
if ( ! afinfo )
return - EAFNOSUPPORT ;
ret = afinfo - > callback ( skb , protocol , err ) ;
xfrm_input_put_afinfo ( afinfo ) ;
return ret ;
}
2005-04-17 02:20:36 +04:00
void __secpath_destroy ( struct sec_path * sp )
{
int i ;
for ( i = 0 ; i < sp - > len ; i + + )
2006-04-01 12:54:16 +04:00
xfrm_state_put ( sp - > xvec [ i ] ) ;
2005-04-17 02:20:36 +04:00
kmem_cache_free ( secpath_cachep , sp ) ;
}
EXPORT_SYMBOL ( __secpath_destroy ) ;
struct sec_path * secpath_dup ( struct sec_path * src )
{
struct sec_path * sp ;
2006-12-07 07:33:16 +03:00
sp = kmem_cache_alloc ( secpath_cachep , GFP_ATOMIC ) ;
2005-04-17 02:20:36 +04:00
if ( ! sp )
return NULL ;
sp - > len = 0 ;
if ( src ) {
int i ;
memcpy ( sp , src , sizeof ( * sp ) ) ;
for ( i = 0 ; i < sp - > len ; i + + )
2006-04-01 12:54:16 +04:00
xfrm_state_hold ( sp - > xvec [ i ] ) ;
2005-04-17 02:20:36 +04:00
}
atomic_set ( & sp - > refcnt , 1 ) ;
return sp ;
}
EXPORT_SYMBOL ( secpath_dup ) ;
/* Fetch spi and seq from ipsec header */
2006-09-28 05:47:59 +04:00
int xfrm_parse_spi ( struct sk_buff * skb , u8 nexthdr , __be32 * spi , __be32 * seq )
2005-04-17 02:20:36 +04:00
{
int offset , offset_seq ;
2007-10-18 08:30:34 +04:00
int hlen ;
2005-04-17 02:20:36 +04:00
switch ( nexthdr ) {
case IPPROTO_AH :
2007-10-18 08:30:34 +04:00
hlen = sizeof ( struct ip_auth_hdr ) ;
2005-04-17 02:20:36 +04:00
offset = offsetof ( struct ip_auth_hdr , spi ) ;
offset_seq = offsetof ( struct ip_auth_hdr , seq_no ) ;
break ;
case IPPROTO_ESP :
2007-10-18 08:30:34 +04:00
hlen = sizeof ( struct ip_esp_hdr ) ;
2005-04-17 02:20:36 +04:00
offset = offsetof ( struct ip_esp_hdr , spi ) ;
offset_seq = offsetof ( struct ip_esp_hdr , seq_no ) ;
break ;
case IPPROTO_COMP :
if ( ! pskb_may_pull ( skb , sizeof ( struct ip_comp_hdr ) ) )
return - EINVAL ;
2013-12-24 05:43:47 +04:00
* spi = htonl ( ntohs ( * ( __be16 * ) ( skb_transport_header ( skb ) + 2 ) ) ) ;
2005-04-17 02:20:36 +04:00
* seq = 0 ;
return 0 ;
default :
return 1 ;
}
2007-10-18 08:30:34 +04:00
if ( ! pskb_may_pull ( skb , hlen ) )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2013-12-24 05:43:47 +04:00
* spi = * ( __be32 * ) ( skb_transport_header ( skb ) + offset ) ;
* seq = * ( __be32 * ) ( skb_transport_header ( skb ) + offset_seq ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
2007-11-14 08:41:28 +03:00
int xfrm_prepare_input ( struct xfrm_state * x , struct sk_buff * skb )
{
2008-03-25 00:51:51 +03:00
struct xfrm_mode * inner_mode = x - > inner_mode ;
2007-11-14 08:41:28 +03:00
int err ;
err = x - > outer_mode - > afinfo - > extract_input ( x , skb ) ;
if ( err )
return err ;
2008-03-25 00:51:51 +03:00
if ( x - > sel . family = = AF_UNSPEC ) {
inner_mode = xfrm_ip2inner_mode ( x , XFRM_MODE_SKB_CB ( skb ) - > protocol ) ;
if ( inner_mode = = NULL )
return - EAFNOSUPPORT ;
}
skb - > protocol = inner_mode - > afinfo - > eth_proto ;
return inner_mode - > input2 ( x , skb ) ;
2007-11-14 08:41:28 +03:00
}
EXPORT_SYMBOL ( xfrm_prepare_input ) ;
2007-11-14 08:44:23 +03:00
int xfrm_input ( struct sk_buff * skb , int nexthdr , __be32 spi , int encap_type )
{
2008-11-26 04:37:56 +03:00
struct net * net = dev_net ( skb - > dev ) ;
2007-11-14 08:44:23 +03:00
int err ;
__be32 seq ;
2011-03-08 03:09:51 +03:00
__be32 seq_hi ;
2014-02-21 11:41:08 +04:00
struct xfrm_state * x = NULL ;
2007-11-20 05:50:17 +03:00
xfrm_address_t * daddr ;
2008-03-25 00:51:51 +03:00
struct xfrm_mode * inner_mode ;
2007-12-04 09:54:12 +03:00
unsigned int family ;
2007-11-14 08:44:23 +03:00
int decaps = 0 ;
2007-11-20 05:50:17 +03:00
int async = 0 ;
/* A negative encap_type indicates async resumption. */
if ( encap_type < 0 ) {
async = 1 ;
2007-12-11 12:53:43 +03:00
x = xfrm_input_state ( skb ) ;
2011-03-08 03:06:31 +03:00
seq = XFRM_SKB_CB ( skb ) - > seq . input . low ;
2014-02-21 11:41:08 +04:00
family = x - > outer_mode - > afinfo - > family ;
2007-11-20 05:50:17 +03:00
goto resume ;
}
2007-11-14 08:44:23 +03:00
2014-02-21 11:41:08 +04:00
daddr = ( xfrm_address_t * ) ( skb_network_header ( skb ) +
XFRM_SPI_SKB_CB ( skb ) - > daddroff ) ;
family = XFRM_SPI_SKB_CB ( skb ) - > family ;
2007-11-14 08:44:55 +03:00
/* Allocate new secpath or COW existing one. */
if ( ! skb - > sp | | atomic_read ( & skb - > sp - > refcnt ) ! = 1 ) {
struct sec_path * sp ;
sp = secpath_dup ( skb - > sp ) ;
2007-12-21 07:43:36 +03:00
if ( ! sp ) {
2008-11-26 04:59:52 +03:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINERROR ) ;
2007-11-14 08:44:55 +03:00
goto drop ;
2007-12-21 07:43:36 +03:00
}
2007-11-14 08:44:55 +03:00
if ( skb - > sp )
secpath_put ( skb - > sp ) ;
skb - > sp = sp ;
}
2007-11-14 08:44:23 +03:00
seq = 0 ;
2007-12-21 07:43:36 +03:00
if ( ! spi & & ( err = xfrm_parse_spi ( skb , nexthdr , & spi , & seq ) ) ! = 0 ) {
2008-11-26 04:59:52 +03:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINHDRERROR ) ;
2007-11-14 08:44:23 +03:00
goto drop ;
2007-12-21 07:43:36 +03:00
}
2007-11-14 08:44:23 +03:00
do {
2007-12-21 07:43:36 +03:00
if ( skb - > sp - > len = = XFRM_MAX_DEPTH ) {
2008-11-26 04:59:52 +03:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINBUFFERERROR ) ;
2007-11-14 08:44:23 +03:00
goto drop ;
2007-12-21 07:43:36 +03:00
}
2007-11-14 08:44:23 +03:00
2010-02-23 03:20:22 +03:00
x = xfrm_state_lookup ( net , skb - > mark , daddr , spi , nexthdr , family ) ;
2007-12-21 07:43:36 +03:00
if ( x = = NULL ) {
2008-11-26 04:59:52 +03:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINNOSTATES ) ;
2007-12-22 01:58:11 +03:00
xfrm_audit_state_notfound ( skb , family , spi , seq ) ;
2007-11-14 08:44:23 +03:00
goto drop ;
2007-12-21 07:43:36 +03:00
}
2007-11-14 08:44:23 +03:00
2007-11-14 08:44:55 +03:00
skb - > sp - > xvec [ skb - > sp - > len + + ] = x ;
2014-02-21 11:41:09 +04:00
if ( xfrm_tunnel_check ( skb , x , family ) ) {
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINSTATEMODEERROR ) ;
goto drop ;
}
2007-11-14 08:44:23 +03:00
spin_lock ( & x - > lock ) ;
2013-06-06 06:15:54 +04:00
if ( unlikely ( x - > km . state = = XFRM_STATE_ACQ ) ) {
XFRM_INC_STATS ( net , LINUX_MIB_XFRMACQUIREERROR ) ;
goto drop_unlock ;
}
2007-12-21 07:43:36 +03:00
if ( unlikely ( x - > km . state ! = XFRM_STATE_VALID ) ) {
2008-11-26 04:59:52 +03:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINSTATEINVALID ) ;
2007-11-14 08:44:23 +03:00
goto drop_unlock ;
2007-12-21 07:43:36 +03:00
}
2007-11-14 08:44:23 +03:00
2008-12-19 06:23:56 +03:00
if ( ( x - > encap ? x - > encap - > encap_type : 0 ) ! = encap_type ) {
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINSTATEMISMATCH ) ;
goto drop_unlock ;
}
2011-03-28 23:45:52 +04:00
if ( x - > repl - > check ( x , skb , seq ) ) {
2008-11-26 04:59:52 +03:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINSTATESEQERROR ) ;
2007-11-14 08:44:23 +03:00
goto drop_unlock ;
2007-12-21 07:43:36 +03:00
}
2007-11-14 08:44:23 +03:00
2007-12-21 07:43:36 +03:00
if ( xfrm_state_check_expire ( x ) ) {
2008-11-26 04:59:52 +03:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINSTATEEXPIRED ) ;
2007-11-14 08:44:23 +03:00
goto drop_unlock ;
2007-12-21 07:43:36 +03:00
}
2007-11-14 08:44:23 +03:00
2007-11-14 08:45:58 +03:00
spin_unlock ( & x - > lock ) ;
2011-03-08 03:09:51 +03:00
seq_hi = htonl ( xfrm_replay_seqhi ( x , seq ) ) ;
2011-03-08 03:06:31 +03:00
XFRM_SKB_CB ( skb ) - > seq . input . low = seq ;
2011-03-08 03:09:51 +03:00
XFRM_SKB_CB ( skb ) - > seq . input . hi = seq_hi ;
2007-11-20 05:50:17 +03:00
2011-03-16 00:08:28 +03:00
skb_dst_force ( skb ) ;
2007-11-14 08:44:23 +03:00
nexthdr = x - > type - > input ( x , skb ) ;
2007-11-14 08:45:58 +03:00
2007-11-20 05:50:17 +03:00
if ( nexthdr = = - EINPROGRESS )
return 0 ;
resume :
2007-11-14 08:45:58 +03:00
spin_lock ( & x - > lock ) ;
2007-12-17 02:55:02 +03:00
if ( nexthdr < = 0 ) {
2007-12-31 08:10:30 +03:00
if ( nexthdr = = - EBADMSG ) {
xfrm_audit_state_icvfail ( x , skb ,
x - > type - > proto ) ;
2007-12-17 02:55:02 +03:00
x - > stats . integrity_failed + + ;
2007-12-31 08:10:30 +03:00
}
2008-11-26 04:59:52 +03:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINSTATEPROTOERROR ) ;
2007-11-14 08:44:23 +03:00
goto drop_unlock ;
2007-12-17 02:55:02 +03:00
}
2007-11-14 08:44:23 +03:00
/* only the first xfrm gets the encap type */
encap_type = 0 ;
2012-09-04 04:03:29 +04:00
if ( async & & x - > repl - > recheck ( x , skb , seq ) ) {
2011-09-21 03:38:58 +04:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINSTATESEQERROR ) ;
goto drop_unlock ;
}
2011-03-08 03:08:32 +03:00
x - > repl - > advance ( x , seq ) ;
2007-11-14 08:44:23 +03:00
x - > curlft . bytes + = skb - > len ;
x - > curlft . packets + + ;
spin_unlock ( & x - > lock ) ;
2007-11-20 05:47:58 +03:00
XFRM_MODE_SKB_CB ( skb ) - > protocol = nexthdr ;
2008-03-25 00:51:51 +03:00
inner_mode = x - > inner_mode ;
if ( x - > sel . family = = AF_UNSPEC ) {
inner_mode = xfrm_ip2inner_mode ( x , XFRM_MODE_SKB_CB ( skb ) - > protocol ) ;
if ( inner_mode = = NULL )
goto drop ;
}
if ( inner_mode - > input ( x , skb ) ) {
2008-11-26 04:59:52 +03:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINSTATEMODEERROR ) ;
2007-11-14 08:44:23 +03:00
goto drop ;
2007-12-21 07:43:36 +03:00
}
2007-11-14 08:44:23 +03:00
if ( x - > outer_mode - > flags & XFRM_MODE_FLAG_TUNNEL ) {
decaps = 1 ;
break ;
}
2007-11-20 05:50:17 +03:00
/*
* We need the inner address . However , we only get here for
* transport mode so the outer address is identical .
*/
daddr = & x - > id . daddr ;
2007-12-04 09:54:12 +03:00
family = x - > outer_mode - > afinfo - > family ;
2007-11-20 05:50:17 +03:00
2007-11-14 08:44:23 +03:00
err = xfrm_parse_spi ( skb , nexthdr , & spi , & seq ) ;
2007-12-21 07:43:36 +03:00
if ( err < 0 ) {
2008-11-26 04:59:52 +03:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMINHDRERROR ) ;
2007-11-14 08:44:23 +03:00
goto drop ;
2007-12-21 07:43:36 +03:00
}
2007-11-14 08:44:23 +03:00
} while ( ! err ) ;
2014-02-21 11:41:08 +04:00
err = xfrm_rcv_cb ( skb , family , x - > type - > proto , 0 ) ;
if ( err )
goto drop ;
2007-11-14 08:44:23 +03:00
nf_reset ( skb ) ;
if ( decaps ) {
2009-06-02 09:19:30 +04:00
skb_dst_drop ( skb ) ;
2007-11-14 08:44:23 +03:00
netif_rx ( skb ) ;
return 0 ;
} else {
2007-11-20 05:50:17 +03:00
return x - > inner_mode - > afinfo - > transport_finish ( skb , async ) ;
2007-11-14 08:44:23 +03:00
}
drop_unlock :
spin_unlock ( & x - > lock ) ;
drop :
2014-02-21 11:41:08 +04:00
xfrm_rcv_cb ( skb , family , x & & x - > type ? x - > type - > proto : nexthdr , - 1 ) ;
2007-11-14 08:44:23 +03:00
kfree_skb ( skb ) ;
return 0 ;
}
EXPORT_SYMBOL ( xfrm_input ) ;
2007-11-20 05:50:17 +03:00
int xfrm_input_resume ( struct sk_buff * skb , int nexthdr )
{
return xfrm_input ( skb , nexthdr , 0 , - 1 ) ;
}
EXPORT_SYMBOL ( xfrm_input_resume ) ;
2005-04-17 02:20:36 +04:00
void __init xfrm_input_init ( void )
{
secpath_cachep = kmem_cache_create ( " secpath_cache " ,
sizeof ( struct sec_path ) ,
2006-08-27 06:25:52 +04:00
0 , SLAB_HWCACHE_ALIGN | SLAB_PANIC ,
2007-07-20 05:11:58 +04:00
NULL ) ;
2005-04-17 02:20:36 +04:00
}