2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2015-07-21 11:43:46 +03:00
/*
* lwtunnel Infrastructure for light weight tunnels like mpls
*
* Authors : Roopa Prabhu , < roopa @ cumulusnetworks . com >
*/
# include <linux/capability.h>
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/slab.h>
# include <linux/uaccess.h>
# include <linux/skbuff.h>
# include <linux/netdevice.h>
# include <linux/lwtunnel.h>
# include <linux/in.h>
# include <linux/init.h>
# include <linux/err.h>
# include <net/lwtunnel.h>
# include <net/rtnetlink.h>
2015-07-21 11:43:49 +03:00
# include <net/ip6_fib.h>
2019-04-20 19:28:20 +03:00
# include <net/rtnh.h>
2015-07-21 11:43:46 +03:00
2016-02-19 12:43:16 +03:00
# ifdef CONFIG_MODULES
static const char * lwtunnel_encap_str ( enum lwtunnel_encap_types encap_type )
{
/* Only lwt encaps implemented without using an interface for
* the encap need to return a string here .
*/
switch ( encap_type ) {
case LWTUNNEL_ENCAP_MPLS :
return " MPLS " ;
case LWTUNNEL_ENCAP_ILA :
return " ILA " ;
2016-11-08 16:57:41 +03:00
case LWTUNNEL_ENCAP_SEG6 :
return " SEG6 " ;
2016-11-30 19:10:10 +03:00
case LWTUNNEL_ENCAP_BPF :
return " BPF " ;
2017-08-05 13:38:26 +03:00
case LWTUNNEL_ENCAP_SEG6_LOCAL :
return " SEG6LOCAL " ;
2020-03-28 01:00:22 +03:00
case LWTUNNEL_ENCAP_RPL :
return " RPL " ;
2016-02-19 12:43:16 +03:00
case LWTUNNEL_ENCAP_IP6 :
case LWTUNNEL_ENCAP_IP :
case LWTUNNEL_ENCAP_NONE :
case __LWTUNNEL_ENCAP_MAX :
/* should not have got here */
WARN_ON ( 1 ) ;
break ;
}
return NULL ;
}
# endif /* CONFIG_MODULES */
2015-07-21 11:43:46 +03:00
struct lwtunnel_state * lwtunnel_state_alloc ( int encap_len )
{
struct lwtunnel_state * lws ;
lws = kzalloc ( sizeof ( * lws ) + encap_len , GFP_ATOMIC ) ;
return lws ;
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_state_alloc ) ;
2015-07-21 11:43:46 +03:00
2015-07-29 10:45:40 +03:00
static const struct lwtunnel_encap_ops __rcu *
2015-07-21 11:43:46 +03:00
lwtun_encaps [ LWTUNNEL_ENCAP_MAX + 1 ] __read_mostly ;
int lwtunnel_encap_add_ops ( const struct lwtunnel_encap_ops * ops ,
unsigned int num )
{
if ( num > LWTUNNEL_ENCAP_MAX )
return - ERANGE ;
return ! cmpxchg ( ( const struct lwtunnel_encap_ops * * )
& lwtun_encaps [ num ] ,
NULL , ops ) ? 0 : - 1 ;
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_encap_add_ops ) ;
2015-07-21 11:43:46 +03:00
int lwtunnel_encap_del_ops ( const struct lwtunnel_encap_ops * ops ,
unsigned int encap_type )
{
int ret ;
if ( encap_type = = LWTUNNEL_ENCAP_NONE | |
encap_type > LWTUNNEL_ENCAP_MAX )
return - ERANGE ;
ret = ( cmpxchg ( ( const struct lwtunnel_encap_ops * * )
& lwtun_encaps [ encap_type ] ,
ops , NULL ) = = ops ) ? 0 : - 1 ;
synchronize_net ( ) ;
return ret ;
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_encap_del_ops ) ;
2015-07-21 11:43:46 +03:00
2020-03-28 01:00:21 +03:00
int lwtunnel_build_state ( struct net * net , u16 encap_type ,
2015-08-24 19:45:41 +03:00
struct nlattr * encap , unsigned int family ,
2017-05-28 01:19:28 +03:00
const void * cfg , struct lwtunnel_state * * lws ,
struct netlink_ext_ack * extack )
2015-07-21 11:43:46 +03:00
{
const struct lwtunnel_encap_ops * ops ;
2017-05-28 01:19:28 +03:00
bool found = false ;
2015-07-21 11:43:46 +03:00
int ret = - EINVAL ;
if ( encap_type = = LWTUNNEL_ENCAP_NONE | |
2017-05-28 01:19:28 +03:00
encap_type > LWTUNNEL_ENCAP_MAX ) {
NL_SET_ERR_MSG_ATTR ( extack , encap ,
" Unknown LWT encapsulation type " ) ;
2015-07-21 11:43:46 +03:00
return ret ;
2017-05-28 01:19:28 +03:00
}
2015-07-21 11:43:46 +03:00
ret = - EOPNOTSUPP ;
rcu_read_lock ( ) ;
ops = rcu_dereference ( lwtun_encaps [ encap_type ] ) ;
2019-02-23 16:32:54 +03:00
if ( likely ( ops & & ops - > build_state & & try_module_get ( ops - > owner ) ) )
2017-05-28 01:19:28 +03:00
found = true ;
2019-02-23 16:32:54 +03:00
rcu_read_unlock ( ) ;
if ( found ) {
2020-03-28 01:00:21 +03:00
ret = ops - > build_state ( net , encap , family , cfg , lws , extack ) ;
2017-01-24 19:26:48 +03:00
if ( ret )
module_put ( ops - > owner ) ;
2019-02-23 16:32:54 +03:00
} else {
/* don't rely on -EOPNOTSUPP to detect match as build_state
* handlers could return it
*/
2017-05-28 01:19:28 +03:00
NL_SET_ERR_MSG_ATTR ( extack , encap ,
" LWT encapsulation type not supported " ) ;
}
2017-01-18 01:57:36 +03:00
return ret ;
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_build_state ) ;
2017-01-18 01:57:36 +03:00
2017-05-28 01:19:27 +03:00
int lwtunnel_valid_encap_type ( u16 encap_type , struct netlink_ext_ack * extack )
2017-01-18 01:57:36 +03:00
{
const struct lwtunnel_encap_ops * ops ;
int ret = - EINVAL ;
if ( encap_type = = LWTUNNEL_ENCAP_NONE | |
2017-05-28 01:19:27 +03:00
encap_type > LWTUNNEL_ENCAP_MAX ) {
NL_SET_ERR_MSG ( extack , " Unknown lwt encapsulation type " ) ;
2017-01-18 01:57:36 +03:00
return ret ;
2017-05-28 01:19:27 +03:00
}
2017-01-18 01:57:36 +03:00
rcu_read_lock ( ) ;
ops = rcu_dereference ( lwtun_encaps [ encap_type ] ) ;
rcu_read_unlock ( ) ;
2016-02-19 12:43:16 +03:00
# ifdef CONFIG_MODULES
if ( ! ops ) {
const char * encap_type_str = lwtunnel_encap_str ( encap_type ) ;
if ( encap_type_str ) {
2017-01-18 01:57:36 +03:00
__rtnl_unlock ( ) ;
2016-02-19 12:43:16 +03:00
request_module ( " rtnl-lwt-%s " , encap_type_str ) ;
2017-01-18 01:57:36 +03:00
rtnl_lock ( ) ;
2016-02-19 12:43:16 +03:00
rcu_read_lock ( ) ;
ops = rcu_dereference ( lwtun_encaps [ encap_type ] ) ;
2017-01-18 01:57:36 +03:00
rcu_read_unlock ( ) ;
2016-02-19 12:43:16 +03:00
}
}
# endif
2017-05-28 01:19:27 +03:00
ret = ops ? 0 : - EOPNOTSUPP ;
if ( ret < 0 )
NL_SET_ERR_MSG ( extack , " lwt encapsulation type not supported " ) ;
return ret ;
2017-01-18 01:57:36 +03:00
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_valid_encap_type ) ;
2015-07-21 11:43:46 +03:00
2017-05-28 01:19:27 +03:00
int lwtunnel_valid_encap_type_attr ( struct nlattr * attr , int remaining ,
struct netlink_ext_ack * extack )
2017-01-18 01:57:36 +03:00
{
struct rtnexthop * rtnh = ( struct rtnexthop * ) attr ;
struct nlattr * nla_entype ;
struct nlattr * attrs ;
u16 encap_type ;
int attrlen ;
while ( rtnh_ok ( rtnh , remaining ) ) {
attrlen = rtnh_attrlen ( rtnh ) ;
if ( attrlen > 0 ) {
attrs = rtnh_attrs ( rtnh ) ;
nla_entype = nla_find ( attrs , attrlen , RTA_ENCAP_TYPE ) ;
if ( nla_entype ) {
encap_type = nla_get_u16 ( nla_entype ) ;
2017-05-28 01:19:27 +03:00
if ( lwtunnel_valid_encap_type ( encap_type ,
extack ) ! = 0 )
2017-01-18 01:57:36 +03:00
return - EOPNOTSUPP ;
}
}
rtnh = rtnh_next ( rtnh , & remaining ) ;
}
return 0 ;
2015-07-21 11:43:46 +03:00
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_valid_encap_type_attr ) ;
2015-07-21 11:43:46 +03:00
2016-10-14 21:25:36 +03:00
void lwtstate_free ( struct lwtunnel_state * lws )
{
const struct lwtunnel_encap_ops * ops = lwtun_encaps [ lws - > type ] ;
if ( ops - > destroy_state ) {
ops - > destroy_state ( lws ) ;
kfree_rcu ( lws , rcu ) ;
} else {
kfree ( lws ) ;
}
2017-01-24 19:26:48 +03:00
module_put ( ops - > owner ) ;
2016-10-14 21:25:36 +03:00
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtstate_free ) ;
2016-10-14 21:25:36 +03:00
2019-04-23 18:23:41 +03:00
int lwtunnel_fill_encap ( struct sk_buff * skb , struct lwtunnel_state * lwtstate ,
int encap_attr , int encap_type_attr )
2015-07-21 11:43:46 +03:00
{
const struct lwtunnel_encap_ops * ops ;
struct nlattr * nest ;
2017-04-28 16:03:48 +03:00
int ret ;
2015-07-21 11:43:46 +03:00
if ( ! lwtstate )
return 0 ;
if ( lwtstate - > type = = LWTUNNEL_ENCAP_NONE | |
lwtstate - > type > LWTUNNEL_ENCAP_MAX )
return 0 ;
2019-04-26 12:13:06 +03:00
nest = nla_nest_start_noflag ( skb , encap_attr ) ;
2017-04-23 09:28:37 +03:00
if ( ! nest )
2017-04-28 16:03:48 +03:00
return - EMSGSIZE ;
ret = - EOPNOTSUPP ;
2015-07-21 11:43:46 +03:00
rcu_read_lock ( ) ;
ops = rcu_dereference ( lwtun_encaps [ lwtstate - > type ] ) ;
if ( likely ( ops & & ops - > fill_encap ) )
ret = ops - > fill_encap ( skb , lwtstate ) ;
rcu_read_unlock ( ) ;
if ( ret )
goto nla_put_failure ;
nla_nest_end ( skb , nest ) ;
2019-04-23 18:23:41 +03:00
ret = nla_put_u16 ( skb , encap_type_attr , lwtstate - > type ) ;
2015-07-21 11:43:46 +03:00
if ( ret )
goto nla_put_failure ;
return 0 ;
nla_put_failure :
nla_nest_cancel ( skb , nest ) ;
return ( ret = = - EOPNOTSUPP ? 0 : ret ) ;
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_fill_encap ) ;
2015-07-21 11:43:46 +03:00
int lwtunnel_get_encap_size ( struct lwtunnel_state * lwtstate )
{
const struct lwtunnel_encap_ops * ops ;
int ret = 0 ;
if ( ! lwtstate )
return 0 ;
if ( lwtstate - > type = = LWTUNNEL_ENCAP_NONE | |
lwtstate - > type > LWTUNNEL_ENCAP_MAX )
return 0 ;
rcu_read_lock ( ) ;
ops = rcu_dereference ( lwtun_encaps [ lwtstate - > type ] ) ;
if ( likely ( ops & & ops - > get_encap_size ) )
ret = nla_total_size ( ops - > get_encap_size ( lwtstate ) ) ;
rcu_read_unlock ( ) ;
return ret ;
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_get_encap_size ) ;
2015-07-21 11:43:46 +03:00
int lwtunnel_cmp_encap ( struct lwtunnel_state * a , struct lwtunnel_state * b )
{
const struct lwtunnel_encap_ops * ops ;
int ret = 0 ;
if ( ! a & & ! b )
return 0 ;
if ( ! a | | ! b )
return 1 ;
if ( a - > type ! = b - > type )
return 1 ;
if ( a - > type = = LWTUNNEL_ENCAP_NONE | |
a - > type > LWTUNNEL_ENCAP_MAX )
return 0 ;
rcu_read_lock ( ) ;
ops = rcu_dereference ( lwtun_encaps [ a - > type ] ) ;
if ( likely ( ops & & ops - > cmp_encap ) )
ret = ops - > cmp_encap ( a , b ) ;
rcu_read_unlock ( ) ;
return ret ;
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_cmp_encap ) ;
2015-07-21 11:43:49 +03:00
2015-10-08 00:48:47 +03:00
int lwtunnel_output ( struct net * net , struct sock * sk , struct sk_buff * skb )
2015-07-21 11:43:49 +03:00
{
2015-08-20 14:56:25 +03:00
struct dst_entry * dst = skb_dst ( skb ) ;
2015-07-21 11:43:49 +03:00
const struct lwtunnel_encap_ops * ops ;
2015-08-20 14:56:25 +03:00
struct lwtunnel_state * lwtstate ;
2015-07-21 11:43:49 +03:00
int ret = - EINVAL ;
2015-08-20 14:56:25 +03:00
if ( ! dst )
2015-07-21 11:43:49 +03:00
goto drop ;
2015-08-20 14:56:25 +03:00
lwtstate = dst - > lwtstate ;
2015-07-21 11:43:49 +03:00
if ( lwtstate - > type = = LWTUNNEL_ENCAP_NONE | |
lwtstate - > type > LWTUNNEL_ENCAP_MAX )
return 0 ;
ret = - EOPNOTSUPP ;
rcu_read_lock ( ) ;
ops = rcu_dereference ( lwtun_encaps [ lwtstate - > type ] ) ;
if ( likely ( ops & & ops - > output ) )
2015-10-08 00:48:47 +03:00
ret = ops - > output ( net , sk , skb ) ;
2015-07-21 11:43:49 +03:00
rcu_read_unlock ( ) ;
if ( ret = = - EOPNOTSUPP )
goto drop ;
return ret ;
drop :
2015-07-27 11:07:47 +03:00
kfree_skb ( skb ) ;
2015-07-21 11:43:49 +03:00
return ret ;
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_output ) ;
2015-08-17 23:42:24 +03:00
2016-08-25 06:10:43 +03:00
int lwtunnel_xmit ( struct sk_buff * skb )
{
struct dst_entry * dst = skb_dst ( skb ) ;
const struct lwtunnel_encap_ops * ops ;
struct lwtunnel_state * lwtstate ;
int ret = - EINVAL ;
if ( ! dst )
goto drop ;
lwtstate = dst - > lwtstate ;
if ( lwtstate - > type = = LWTUNNEL_ENCAP_NONE | |
lwtstate - > type > LWTUNNEL_ENCAP_MAX )
return 0 ;
ret = - EOPNOTSUPP ;
rcu_read_lock ( ) ;
ops = rcu_dereference ( lwtun_encaps [ lwtstate - > type ] ) ;
if ( likely ( ops & & ops - > xmit ) )
ret = ops - > xmit ( skb ) ;
rcu_read_unlock ( ) ;
if ( ret = = - EOPNOTSUPP )
goto drop ;
return ret ;
drop :
kfree_skb ( skb ) ;
return ret ;
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_xmit ) ;
2016-08-25 06:10:43 +03:00
2015-08-20 14:56:25 +03:00
int lwtunnel_input ( struct sk_buff * skb )
2015-08-17 23:42:24 +03:00
{
2015-08-20 14:56:25 +03:00
struct dst_entry * dst = skb_dst ( skb ) ;
2015-08-17 23:42:24 +03:00
const struct lwtunnel_encap_ops * ops ;
2015-08-20 14:56:25 +03:00
struct lwtunnel_state * lwtstate ;
2015-08-17 23:42:24 +03:00
int ret = - EINVAL ;
2015-08-20 14:56:25 +03:00
if ( ! dst )
2015-08-17 23:42:24 +03:00
goto drop ;
2015-08-20 14:56:25 +03:00
lwtstate = dst - > lwtstate ;
2015-08-17 23:42:24 +03:00
if ( lwtstate - > type = = LWTUNNEL_ENCAP_NONE | |
lwtstate - > type > LWTUNNEL_ENCAP_MAX )
return 0 ;
ret = - EOPNOTSUPP ;
rcu_read_lock ( ) ;
ops = rcu_dereference ( lwtun_encaps [ lwtstate - > type ] ) ;
if ( likely ( ops & & ops - > input ) )
ret = ops - > input ( skb ) ;
rcu_read_unlock ( ) ;
if ( ret = = - EOPNOTSUPP )
goto drop ;
return ret ;
drop :
kfree_skb ( skb ) ;
return ret ;
}
2017-08-05 04:19:18 +03:00
EXPORT_SYMBOL_GPL ( lwtunnel_input ) ;