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 )
{
2009-06-02 05:19:30 +00:00
struct dst_entry * dst = skb_dst ( skb ) ;
2007-11-13 21:33:01 -08:00
int nhead = dst - > header_len + LL_RESERVED_SPACE ( dst - > dev )
2007-10-08 17:25:08 -07:00
- skb_headroom ( skb ) ;
2008-05-12 20:48:31 -07:00
int ntail = dst - > dev - > needed_tailroom - skb_tailroom ( skb ) ;
2007-10-08 17:25:08 -07:00
2008-09-30 02:03:19 -07: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-08 17:25:08 -07:00
}
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
{
2009-06-02 05:19:30 +00:00
struct dst_entry * dst = skb_dst ( skb ) ;
2007-10-08 17:16:30 -07:00
struct xfrm_state * x = dst - > xfrm ;
2008-11-25 17:38:20 -08:00
struct net * net = xs_net ( x ) ;
2007-10-08 17:16:30 -07:00
2007-11-13 21:43:43 -08:00
if ( err < = 0 )
goto resume ;
2007-10-08 17:16:30 -07:00
do {
2007-12-18 22:14:25 -08:00
err = xfrm_state_check_space ( x , skb ) ;
2007-12-24 16:00:09 -08:00
if ( err ) {
2008-11-25 17:59:52 -08:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMOUTERROR ) ;
2007-12-18 22:14:25 -08:00
goto error_nolock ;
2007-12-24 16:00:09 -08:00
}
2007-12-18 22:14:25 -08:00
2007-11-13 21:39:38 -08:00
err = x - > outer_mode - > output ( x , skb ) ;
2007-12-24 16:00:09 -08:00
if ( err ) {
2008-11-25 17:59:52 -08:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMOUTSTATEMODEERROR ) ;
2007-12-18 22:14:25 -08:00
goto error_nolock ;
2007-12-24 16:00:09 -08:00
}
2007-11-13 21:39:38 -08:00
2007-10-08 17:16:30 -07:00
spin_lock_bh ( & x - > lock ) ;
2007-12-18 22:14:25 -08:00
err = xfrm_state_check_expire ( x ) ;
2007-12-24 16:00:09 -08:00
if ( err ) {
2008-11-25 17:59:52 -08:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMOUTSTATEEXPIRED ) ;
2007-10-08 17:16:30 -07:00
goto error ;
2007-12-24 16:00:09 -08:00
}
2007-10-08 17:16:30 -07:00
2007-10-08 17:25:53 -07:00
if ( x - > type - > flags & XFRM_TYPE_REPLAY_PROT ) {
2008-02-12 22:50:35 -08:00
XFRM_SKB_CB ( skb ) - > seq . output = + + x - > replay . oseq ;
2007-12-21 14:59:08 -08:00
if ( unlikely ( x - > replay . oseq = = 0 ) ) {
2008-11-25 17:59:52 -08:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMOUTSTATESEQERROR ) ;
2007-12-21 14:59:08 -08:00
x - > replay . oseq - - ;
2007-12-21 14:58:11 -08:00
xfrm_audit_state_replay_overflow ( x , skb ) ;
2008-01-01 23:52:59 -08:00
err = - EOVERFLOW ;
2007-12-21 14:59:08 -08:00
goto error ;
}
2008-11-25 17:38:20 -08:00
if ( xfrm_aevent_is_on ( net ) )
2007-10-08 17:26:34 -07:00
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-12-30 21:09:38 -08:00
if ( err = = - EINPROGRESS )
goto out_exit ;
2007-11-13 21:43:43 -08:00
resume :
2007-12-20 20:43:36 -08:00
if ( err ) {
2008-11-25 17:59:52 -08:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMOUTSTATEPROTOERROR ) ;
2007-10-09 13:33:35 -07:00
goto error_nolock ;
2007-12-20 20:43:36 -08:00
}
2007-10-09 13:33:35 -07:00
2009-06-02 05:19:30 +00:00
dst = dst_pop ( dst ) ;
if ( ! dst ) {
2008-11-25 17:59:52 -08:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMOUTERROR ) ;
2007-10-08 17:16:30 -07:00
err = - EHOSTUNREACH ;
goto error_nolock ;
}
2009-06-02 05:19:30 +00:00
skb_dst_set ( skb , dst ) ;
2007-10-08 17:16:30 -07:00
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
nf_reset ( skb ) ;
2009-06-02 05:19:30 +00:00
err = skb_dst ( skb ) - > ops - > local_out ( skb ) ;
2007-11-13 21:43:11 -08:00
if ( unlikely ( err ! = 1 ) )
2007-11-13 21:43:43 -08:00
goto out ;
2007-11-13 21:43:11 -08:00
2009-06-02 05:19:30 +00:00
if ( ! skb_dst ( skb ) - > xfrm )
2007-11-13 21:43:11 -08:00
return dst_output ( skb ) ;
2009-06-02 05:19:30 +00:00
err = nf_hook ( skb_dst ( skb ) - > ops - > family ,
2007-11-14 01:57:47 -08:00
NF_INET_POST_ROUTING , skb ,
2009-06-02 05:19:30 +00:00
NULL , skb_dst ( skb ) - > dev , xfrm_output2 ) ;
2007-11-13 21:43:11 -08:00
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 ) ;
2008-04-29 01:03:09 -07:00
if ( IS_ERR ( segs ) )
2007-11-13 21:43:11 -08: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-08 17:16:30 -07:00
}
2007-11-13 21:43:43 -08:00
int xfrm_output ( struct sk_buff * skb )
{
2009-06-02 05:19:30 +00:00
struct net * net = dev_net ( skb_dst ( skb ) - > dev ) ;
2007-11-13 21:43:43 -08:00
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 ) {
2008-11-25 17:59:52 -08:00
XFRM_INC_STATS ( net , LINUX_MIB_XFRMOUTERROR ) ;
2007-11-13 21:43:43 -08:00
kfree_skb ( skb ) ;
return err ;
}
}
return xfrm_output2 ( skb ) ;
}
2008-03-24 14:51:51 -07: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 ,
2009-06-02 05:19:30 +00:00
xfrm_af2proto ( skb_dst ( skb ) - > ops - > family ) ) ;
2008-03-24 14:51:51 -07:00
else
inner_mode = x - > inner_mode ;
if ( inner_mode = = NULL )
return - EAFNOSUPPORT ;
return inner_mode - > afinfo - > extract_output ( x , skb ) ;
}
2007-10-08 17:16:30 -07:00
EXPORT_SYMBOL_GPL ( xfrm_output ) ;
2008-03-24 14:51:51 -07:00
EXPORT_SYMBOL_GPL ( xfrm_inner_extract_output ) ;