2007-10-08 17:16:30 -07: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-13 21:43:11 -08:00
# include <linux/netfilter.h>
2007-10-08 17:16:30 -07:00
# include <linux/skbuff.h>
# include <linux/spinlock.h>
# include <net/dst.h>
# include <net/xfrm.h>
2007-11-13 21:43:43 -08:00
static int xfrm_output2 ( struct sk_buff * skb ) ;
2007-10-08 17:25:08 -07:00
static int xfrm_state_check_space ( struct xfrm_state * x , struct sk_buff * skb )
{
2007-11-13 21:33:01 -08:00
struct dst_entry * dst = skb - > dst ;
int nhead = dst - > header_len + LL_RESERVED_SPACE ( dst - > dev )
2007-10-08 17:25:08 -07:00
- skb_headroom ( skb ) ;
if ( nhead > 0 )
return pskb_expand_head ( skb , nhead , 0 , GFP_ATOMIC ) ;
/* Check tail too... */
return 0 ;
}
static int xfrm_state_check ( struct xfrm_state * x , struct sk_buff * skb )
{
int err = xfrm_state_check_expire ( x ) ;
if ( err < 0 )
goto err ;
err = xfrm_state_check_space ( x , skb ) ;
err :
return err ;
}
2007-11-13 21:43:43 -08:00
static int xfrm_output_one ( struct sk_buff * skb , int err )
2007-10-08 17:16:30 -07:00
{
struct dst_entry * dst = skb - > dst ;
struct xfrm_state * x = dst - > xfrm ;
2007-11-13 21:43:43 -08:00
if ( err < = 0 )
goto resume ;
2007-10-08 17:16:30 -07:00
do {
2007-11-13 21:39:38 -08:00
err = x - > outer_mode - > output ( x , skb ) ;
if ( err )
goto error ;
2007-10-08 17:16:30 -07:00
spin_lock_bh ( & x - > lock ) ;
err = xfrm_state_check ( x , skb ) ;
if ( err )
goto error ;
2007-10-08 17:25:53 -07:00
if ( x - > type - > flags & XFRM_TYPE_REPLAY_PROT ) {
XFRM_SKB_CB ( skb ) - > seq = + + x - > replay . oseq ;
2007-10-08 17:26:34 -07:00
if ( xfrm_aevent_is_on ( ) )
xfrm_replay_notify ( x , XFRM_REPLAY_UPDATE ) ;
2007-10-08 17:25:53 -07:00
}
2007-10-08 17:16:30 -07:00
x - > curlft . bytes + = skb - > len ;
x - > curlft . packets + + ;
spin_unlock_bh ( & x - > lock ) ;
2007-10-09 13:33:35 -07:00
err = x - > type - > output ( x , skb ) ;
2007-11-13 21:43:43 -08:00
resume :
2007-10-09 13:33:35 -07:00
if ( err )
goto error_nolock ;
2007-10-08 17:16:30 -07:00
if ( ! ( skb - > dst = dst_pop ( dst ) ) ) {
err = - EHOSTUNREACH ;
goto error_nolock ;
}
dst = skb - > dst ;
x = dst - > xfrm ;
2007-10-17 21:35:51 -07:00
} while ( x & & ! ( x - > outer_mode - > flags & XFRM_MODE_FLAG_TUNNEL ) ) ;
2007-10-08 17:16:30 -07:00
err = 0 ;
2007-11-13 21:43:11 -08:00
out_exit :
2007-10-08 17:16:30 -07:00
return err ;
error :
spin_unlock_bh ( & x - > lock ) ;
2007-11-13 21:43:11 -08:00
error_nolock :
kfree_skb ( skb ) ;
goto out_exit ;
}
2007-11-13 21:43:43 -08:00
int xfrm_output_resume ( struct sk_buff * skb , int err )
2007-11-13 21:43:11 -08:00
{
2007-11-13 21:43:43 -08:00
while ( likely ( ( err = xfrm_output_one ( skb , err ) ) = = 0 ) ) {
2007-11-13 21:43:11 -08:00
struct xfrm_state * x ;
nf_reset ( skb ) ;
err = skb - > dst - > ops - > local_out ( skb ) ;
if ( unlikely ( err ! = 1 ) )
2007-11-13 21:43:43 -08:00
goto out ;
2007-11-13 21:43:11 -08:00
x = skb - > dst - > xfrm ;
if ( ! x )
return dst_output ( skb ) ;
err = nf_hook ( x - > inner_mode - > afinfo - > family ,
x - > inner_mode - > afinfo - > nf_post_routing , skb ,
NULL , skb - > dst - > dev , xfrm_output2 ) ;
if ( unlikely ( err ! = 1 ) )
2007-11-13 21:43:43 -08:00
goto out ;
2007-11-13 21:43:11 -08:00
}
2007-11-13 21:43:43 -08:00
if ( err = = - EINPROGRESS )
err = 0 ;
out :
2007-11-13 21:43:11 -08:00
return err ;
}
2007-11-13 21:43:43 -08:00
EXPORT_SYMBOL_GPL ( xfrm_output_resume ) ;
2007-11-13 21:43:11 -08:00
2007-11-13 21:43:43 -08:00
static int xfrm_output2 ( struct sk_buff * skb )
2007-11-13 21:43:11 -08:00
{
2007-11-13 21:43:43 -08:00
return xfrm_output_resume ( skb , 1 ) ;
}
2007-11-13 21:43:11 -08:00
2007-11-13 21:43:43 -08:00
static int xfrm_output_gso ( struct sk_buff * skb )
{
struct sk_buff * segs ;
2007-11-13 21:43:11 -08:00
segs = skb_gso_segment ( skb , 0 ) ;
kfree_skb ( skb ) ;
if ( unlikely ( IS_ERR ( segs ) ) )
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-08 17:16:30 -07:00
}
2007-11-13 21:43:43 -08: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 ) {
kfree_skb ( skb ) ;
return err ;
}
}
return xfrm_output2 ( skb ) ;
}
2007-10-08 17:16:30 -07:00
EXPORT_SYMBOL_GPL ( xfrm_output ) ;