2007-10-09 04:16:30 +04:00
/*
* xfrm_output . c - Common IPsec encapsulation code .
*
* Copyright ( c ) 2007 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 .
*/
# include <linux/errno.h>
# include <linux/module.h>
# include <linux/netdevice.h>
2007-11-14 08:43:11 +03:00
# include <linux/netfilter.h>
2007-10-09 04:16:30 +04:00
# include <linux/skbuff.h>
# include <linux/spinlock.h>
# include <net/dst.h>
# include <net/xfrm.h>
2007-11-14 08:43:43 +03:00
static int xfrm_output2 ( struct sk_buff * skb ) ;
2007-10-09 04:25:08 +04:00
static int xfrm_state_check_space ( struct xfrm_state * x , struct sk_buff * skb )
{
2007-11-14 08:33:01 +03:00
struct dst_entry * dst = skb - > dst ;
int nhead = dst - > header_len + LL_RESERVED_SPACE ( dst - > dev )
2007-10-09 04:25:08 +04:00
- skb_headroom ( skb ) ;
2008-05-13 07:48:31 +04:00
int ntail = dst - > dev - > needed_tailroom - skb_tailroom ( skb ) ;
2007-10-09 04:25:08 +04:00
2008-09-30 13:03:19 +04:00
if ( nhead < = 0 ) {
if ( ntail < = 0 )
return 0 ;
nhead = 0 ;
} else if ( ntail < 0 )
ntail = 0 ;
return pskb_expand_head ( skb , nhead , ntail , GFP_ATOMIC ) ;
2007-10-09 04:25:08 +04:00
}
2007-11-14 08:43:43 +03:00
static int xfrm_output_one ( struct sk_buff * skb , int err )
2007-10-09 04:16:30 +04:00
{
struct dst_entry * dst = skb - > dst ;
struct xfrm_state * x = dst - > xfrm ;
2007-11-14 08:43:43 +03:00
if ( err < = 0 )
goto resume ;
2007-10-09 04:16:30 +04:00
do {
2007-12-19 09:14:25 +03:00
err = xfrm_state_check_space ( x , skb ) ;
2007-12-25 03:00:09 +03:00
if ( err ) {
XFRM_INC_STATS ( LINUX_MIB_XFRMOUTERROR ) ;
2007-12-19 09:14:25 +03:00
goto error_nolock ;
2007-12-25 03:00:09 +03:00
}
2007-12-19 09:14:25 +03:00
2007-11-14 08:39:38 +03:00
err = x - > outer_mode - > output ( x , skb ) ;
2007-12-25 03:00:09 +03:00
if ( err ) {
XFRM_INC_STATS ( LINUX_MIB_XFRMOUTSTATEMODEERROR ) ;
2007-12-19 09:14:25 +03:00
goto error_nolock ;
2007-12-25 03:00:09 +03:00
}
2007-11-14 08:39:38 +03:00
2007-10-09 04:16:30 +04:00
spin_lock_bh ( & x - > lock ) ;
2007-12-19 09:14:25 +03:00
err = xfrm_state_check_expire ( x ) ;
2007-12-25 03:00:09 +03:00
if ( err ) {
XFRM_INC_STATS ( LINUX_MIB_XFRMOUTSTATEEXPIRED ) ;
2007-10-09 04:16:30 +04:00
goto error ;
2007-12-25 03:00:09 +03:00
}
2007-10-09 04:16:30 +04:00
2007-10-09 04:25:53 +04:00
if ( x - > type - > flags & XFRM_TYPE_REPLAY_PROT ) {
2008-02-13 09:50:35 +03:00
XFRM_SKB_CB ( skb ) - > seq . output = + + x - > replay . oseq ;
2007-12-22 01:59:08 +03:00
if ( unlikely ( x - > replay . oseq = = 0 ) ) {
2008-02-01 04:14:58 +03:00
XFRM_INC_STATS ( LINUX_MIB_XFRMOUTSTATESEQERROR ) ;
2007-12-22 01:59:08 +03:00
x - > replay . oseq - - ;
2007-12-22 01:58:11 +03:00
xfrm_audit_state_replay_overflow ( x , skb ) ;
2008-01-02 10:52:59 +03:00
err = - EOVERFLOW ;
2007-12-22 01:59:08 +03:00
goto error ;
}
2007-10-09 04:26:34 +04:00
if ( xfrm_aevent_is_on ( ) )
xfrm_replay_notify ( x , XFRM_REPLAY_UPDATE ) ;
2007-10-09 04:25:53 +04:00
}
2007-10-09 04:16:30 +04:00
x - > curlft . bytes + = skb - > len ;
x - > curlft . packets + + ;
spin_unlock_bh ( & x - > lock ) ;
2007-10-10 00:33:35 +04:00
err = x - > type - > output ( x , skb ) ;
2007-12-31 08:09:38 +03:00
if ( err = = - EINPROGRESS )
goto out_exit ;
2007-11-14 08:43:43 +03:00
resume :
2007-12-21 07:43:36 +03:00
if ( err ) {
XFRM_INC_STATS ( LINUX_MIB_XFRMOUTSTATEPROTOERROR ) ;
2007-10-10 00:33:35 +04:00
goto error_nolock ;
2007-12-21 07:43:36 +03:00
}
2007-10-10 00:33:35 +04:00
2007-10-09 04:16:30 +04:00
if ( ! ( skb - > dst = dst_pop ( dst ) ) ) {
2007-12-21 07:43:36 +03:00
XFRM_INC_STATS ( LINUX_MIB_XFRMOUTERROR ) ;
2007-10-09 04:16:30 +04:00
err = - EHOSTUNREACH ;
goto error_nolock ;
}
dst = skb - > dst ;
x = dst - > xfrm ;
2007-10-18 08:35:51 +04:00
} while ( x & & ! ( x - > outer_mode - > flags & XFRM_MODE_FLAG_TUNNEL ) ) ;
2007-10-09 04:16:30 +04:00
err = 0 ;
2007-11-14 08:43:11 +03:00
out_exit :
2007-10-09 04:16:30 +04:00
return err ;
error :
spin_unlock_bh ( & x - > lock ) ;
2007-11-14 08:43:11 +03:00
error_nolock :
kfree_skb ( skb ) ;
goto out_exit ;
}
2007-11-14 08:43:43 +03:00
int xfrm_output_resume ( struct sk_buff * skb , int err )
2007-11-14 08:43:11 +03:00
{
2007-11-14 08:43:43 +03:00
while ( likely ( ( err = xfrm_output_one ( skb , err ) ) = = 0 ) ) {
2007-11-14 08:43:11 +03:00
nf_reset ( skb ) ;
err = skb - > dst - > ops - > local_out ( skb ) ;
if ( unlikely ( err ! = 1 ) )
2007-11-14 08:43:43 +03:00
goto out ;
2007-11-14 08:43:11 +03:00
2008-08-14 00:35:37 +04:00
if ( ! skb - > dst - > xfrm )
2007-11-14 08:43:11 +03:00
return dst_output ( skb ) ;
2008-03-25 00:51:51 +03:00
err = nf_hook ( skb - > dst - > ops - > family ,
2007-11-14 12:57:47 +03:00
NF_INET_POST_ROUTING , skb ,
2007-11-14 08:43:11 +03:00
NULL , skb - > dst - > dev , xfrm_output2 ) ;
if ( unlikely ( err ! = 1 ) )
2007-11-14 08:43:43 +03:00
goto out ;
2007-11-14 08:43:11 +03:00
}
2007-11-14 08:43:43 +03:00
if ( err = = - EINPROGRESS )
err = 0 ;
out :
2007-11-14 08:43:11 +03:00
return err ;
}
2007-11-14 08:43:43 +03:00
EXPORT_SYMBOL_GPL ( xfrm_output_resume ) ;
2007-11-14 08:43:11 +03:00
2007-11-14 08:43:43 +03:00
static int xfrm_output2 ( struct sk_buff * skb )
2007-11-14 08:43:11 +03:00
{
2007-11-14 08:43:43 +03:00
return xfrm_output_resume ( skb , 1 ) ;
}
2007-11-14 08:43:11 +03:00
2007-11-14 08:43:43 +03:00
static int xfrm_output_gso ( struct sk_buff * skb )
{
struct sk_buff * segs ;
2007-11-14 08:43:11 +03:00
segs = skb_gso_segment ( skb , 0 ) ;
kfree_skb ( skb ) ;
2008-04-29 12:03:09 +04:00
if ( IS_ERR ( segs ) )
2007-11-14 08:43:11 +03:00
return PTR_ERR ( segs ) ;
do {
struct sk_buff * nskb = segs - > next ;
int err ;
segs - > next = NULL ;
err = xfrm_output2 ( segs ) ;
if ( unlikely ( err ) ) {
while ( ( segs = nskb ) ) {
nskb = segs - > next ;
segs - > next = NULL ;
kfree_skb ( segs ) ;
}
return err ;
}
segs = nskb ;
} while ( segs ) ;
return 0 ;
2007-10-09 04:16:30 +04:00
}
2007-11-14 08:43:43 +03:00
int xfrm_output ( struct sk_buff * skb )
{
int err ;
if ( skb_is_gso ( skb ) )
return xfrm_output_gso ( skb ) ;
if ( skb - > ip_summed = = CHECKSUM_PARTIAL ) {
err = skb_checksum_help ( skb ) ;
if ( err ) {
2007-12-21 07:43:36 +03:00
XFRM_INC_STATS ( LINUX_MIB_XFRMOUTERROR ) ;
2007-11-14 08:43:43 +03:00
kfree_skb ( skb ) ;
return err ;
}
}
return xfrm_output2 ( skb ) ;
}
2008-03-25 00:51:51 +03:00
int xfrm_inner_extract_output ( struct xfrm_state * x , struct sk_buff * skb )
{
struct xfrm_mode * inner_mode ;
if ( x - > sel . family = = AF_UNSPEC )
inner_mode = xfrm_ip2inner_mode ( x ,
xfrm_af2proto ( skb - > dst - > ops - > family ) ) ;
else
inner_mode = x - > inner_mode ;
if ( inner_mode = = NULL )
return - EAFNOSUPPORT ;
return inner_mode - > afinfo - > extract_output ( x , skb ) ;
}
2007-10-09 04:16:30 +04:00
EXPORT_SYMBOL_GPL ( xfrm_output ) ;
2008-03-25 00:51:51 +03:00
EXPORT_SYMBOL_GPL ( xfrm_inner_extract_output ) ;