2017-04-14 11:05:53 +03:00
/*
* xfrm_device . c - IPsec device offloading code .
*
* Copyright ( c ) 2015 secunet Security Networks AG
*
* Author :
* Steffen Klassert < steffen . klassert @ secunet . com >
*
* 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>
# include <linux/skbuff.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <net/dst.h>
# include <net/xfrm.h>
# include <linux/notifier.h>
2017-04-14 11:06:10 +03:00
int xfrm_dev_state_add ( struct net * net , struct xfrm_state * x ,
struct xfrm_user_offload * xuo )
{
int err ;
struct dst_entry * dst ;
struct net_device * dev ;
struct xfrm_state_offload * xso = & x - > xso ;
xfrm_address_t * saddr ;
xfrm_address_t * daddr ;
if ( ! x - > type_offload )
return 0 ;
/* We don't yet support UDP encapsulation, TFC padding and ESN. */
if ( x - > encap | | x - > tfcpad | | ( x - > props . flags & XFRM_STATE_ESN ) )
return 0 ;
dev = dev_get_by_index ( net , xuo - > ifindex ) ;
if ( ! dev ) {
if ( ! ( xuo - > flags & XFRM_OFFLOAD_INBOUND ) ) {
saddr = & x - > props . saddr ;
daddr = & x - > id . daddr ;
} else {
saddr = & x - > id . daddr ;
daddr = & x - > props . saddr ;
}
dst = __xfrm_dst_lookup ( net , 0 , 0 , saddr , daddr , x - > props . family ) ;
if ( IS_ERR ( dst ) )
return 0 ;
dev = dst - > dev ;
dev_hold ( dev ) ;
dst_release ( dst ) ;
}
if ( ! dev - > xfrmdev_ops | | ! dev - > xfrmdev_ops - > xdo_dev_state_add ) {
dev_put ( dev ) ;
return 0 ;
}
xso - > dev = dev ;
xso - > num_exthdrs = 1 ;
xso - > flags = xuo - > flags ;
err = dev - > xfrmdev_ops - > xdo_dev_state_add ( x ) ;
if ( err ) {
dev_put ( dev ) ;
return err ;
}
return 0 ;
}
EXPORT_SYMBOL_GPL ( xfrm_dev_state_add ) ;
bool xfrm_dev_offload_ok ( struct sk_buff * skb , struct xfrm_state * x )
{
int mtu ;
struct dst_entry * dst = skb_dst ( skb ) ;
struct xfrm_dst * xdst = ( struct xfrm_dst * ) dst ;
struct net_device * dev = x - > xso . dev ;
if ( ! x - > type_offload | | x - > encap )
return false ;
if ( ( x - > xso . offload_handle & & ( dev = = dst - > path - > dev ) ) & &
! dst - > child - > xfrm & & x - > type - > get_mtu ) {
mtu = x - > type - > get_mtu ( x , xdst - > child_mtu_cached ) ;
if ( skb - > len < = mtu )
goto ok ;
if ( skb_is_gso ( skb ) & & skb_gso_validate_mtu ( skb , mtu ) )
goto ok ;
}
return false ;
ok :
if ( dev & & dev - > xfrmdev_ops & & dev - > xfrmdev_ops - > xdo_dev_offload_ok )
return x - > xso . dev - > xfrmdev_ops - > xdo_dev_offload_ok ( skb , x ) ;
return true ;
}
EXPORT_SYMBOL_GPL ( xfrm_dev_offload_ok ) ;
int xfrm_dev_register ( struct net_device * dev )
{
if ( ( dev - > features & NETIF_F_HW_ESP ) & & ! dev - > xfrmdev_ops )
return NOTIFY_BAD ;
if ( ( dev - > features & NETIF_F_HW_ESP_TX_CSUM ) & &
! ( dev - > features & NETIF_F_HW_ESP ) )
return NOTIFY_BAD ;
return NOTIFY_DONE ;
}
static int xfrm_dev_unregister ( struct net_device * dev )
{
return NOTIFY_DONE ;
}
static int xfrm_dev_feat_change ( struct net_device * dev )
{
if ( ( dev - > features & NETIF_F_HW_ESP ) & & ! dev - > xfrmdev_ops )
return NOTIFY_BAD ;
else if ( ! ( dev - > features & NETIF_F_HW_ESP ) )
dev - > xfrmdev_ops = NULL ;
if ( ( dev - > features & NETIF_F_HW_ESP_TX_CSUM ) & &
! ( dev - > features & NETIF_F_HW_ESP ) )
return NOTIFY_BAD ;
return NOTIFY_DONE ;
}
static int xfrm_dev_down ( struct net_device * dev )
{
if ( dev - > hw_features & NETIF_F_HW_ESP )
xfrm_dev_state_flush ( dev_net ( dev ) , dev , true ) ;
xfrm_garbage_collect ( dev_net ( dev ) ) ;
return NOTIFY_DONE ;
}
2017-04-14 11:05:53 +03:00
static int xfrm_dev_event ( struct notifier_block * this , unsigned long event , void * ptr )
{
struct net_device * dev = netdev_notifier_info_to_dev ( ptr ) ;
switch ( event ) {
2017-04-14 11:06:10 +03:00
case NETDEV_REGISTER :
return xfrm_dev_register ( dev ) ;
case NETDEV_UNREGISTER :
return xfrm_dev_unregister ( dev ) ;
case NETDEV_FEAT_CHANGE :
return xfrm_dev_feat_change ( dev ) ;
2017-04-14 11:05:53 +03:00
case NETDEV_DOWN :
2017-04-14 11:06:10 +03:00
return xfrm_dev_down ( dev ) ;
2017-04-14 11:05:53 +03:00
}
return NOTIFY_DONE ;
}
static struct notifier_block xfrm_dev_notifier = {
. notifier_call = xfrm_dev_event ,
} ;
void __net_init xfrm_dev_init ( void )
{
register_netdevice_notifier ( & xfrm_dev_notifier ) ;
}