2018-03-27 18:22:00 -07:00
/*
* Copyright ( c ) 2018 Cumulus Networks . All rights reserved .
* Copyright ( c ) 2018 David Ahern < dsa @ cumulusnetworks . com >
*
* This software is licensed under the GNU General License Version 2 ,
* June 1991 as shown in the file COPYING in the top - level directory of this
* source tree .
*
* THE COPYRIGHT HOLDERS AND / OR OTHER PARTIES PROVIDE THE PROGRAM " AS IS "
* WITHOUT WARRANTY OF ANY KIND , EITHER EXPRESSED OR IMPLIED , INCLUDING ,
* BUT NOT LIMITED TO , THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE . THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE
* OF THE PROGRAM IS WITH YOU . SHOULD THE PROGRAM PROVE DEFECTIVE , YOU ASSUME
* THE COST OF ALL NECESSARY SERVICING , REPAIR OR CORRECTION .
*/
2020-01-14 13:23:15 +02:00
# include <linux/in6.h>
# include <linux/kernel.h>
# include <linux/list.h>
# include <linux/rhashtable.h>
# include <linux/spinlock_types.h>
# include <linux/types.h>
2018-03-27 18:22:00 -07:00
# include <net/fib_notifier.h>
# include <net/ip_fib.h>
# include <net/ip6_fib.h>
# include <net/fib_rules.h>
2019-10-03 11:49:26 +02:00
# include <net/net_namespace.h>
2018-03-27 18:22:00 -07:00
# include "netdevsim.h"
struct nsim_fib_entry {
u64 max ;
u64 num ;
} ;
struct nsim_per_fib_data {
struct nsim_fib_entry fib ;
struct nsim_fib_entry rules ;
} ;
struct nsim_fib_data {
2019-10-03 11:49:26 +02:00
struct notifier_block fib_nb ;
2018-03-27 18:22:00 -07:00
struct nsim_per_fib_data ipv4 ;
struct nsim_per_fib_data ipv6 ;
2020-01-14 13:23:15 +02:00
struct rhashtable fib_rt_ht ;
struct list_head fib_rt_list ;
spinlock_t fib_lock ; /* Protects hashtable, list and accounting */
struct devlink * devlink ;
} ;
struct nsim_fib_rt_key {
unsigned char addr [ sizeof ( struct in6_addr ) ] ;
unsigned char prefix_len ;
int family ;
u32 tb_id ;
} ;
struct nsim_fib_rt {
struct nsim_fib_rt_key key ;
struct rhash_head ht_node ;
struct list_head list ; /* Member of fib_rt_list */
} ;
struct nsim_fib4_rt {
struct nsim_fib_rt common ;
struct fib_info * fi ;
u8 tos ;
u8 type ;
} ;
struct nsim_fib6_rt {
struct nsim_fib_rt common ;
struct list_head nh_list ;
unsigned int nhs ;
} ;
struct nsim_fib6_rt_nh {
struct list_head list ; /* Member of nh_list */
struct fib6_info * rt ;
} ;
static const struct rhashtable_params nsim_fib_rt_ht_params = {
. key_offset = offsetof ( struct nsim_fib_rt , key ) ,
. head_offset = offsetof ( struct nsim_fib_rt , ht_node ) ,
. key_len = sizeof ( struct nsim_fib_rt_key ) ,
. automatic_shrinking = true ,
2018-03-27 18:22:00 -07:00
} ;
2019-10-03 11:49:26 +02:00
u64 nsim_fib_get_val ( struct nsim_fib_data * fib_data ,
enum nsim_resource_id res_id , bool max )
2018-03-27 18:22:00 -07:00
{
struct nsim_fib_entry * entry ;
switch ( res_id ) {
case NSIM_RESOURCE_IPV4_FIB :
entry = & fib_data - > ipv4 . fib ;
break ;
case NSIM_RESOURCE_IPV4_FIB_RULES :
entry = & fib_data - > ipv4 . rules ;
break ;
case NSIM_RESOURCE_IPV6_FIB :
entry = & fib_data - > ipv6 . fib ;
break ;
case NSIM_RESOURCE_IPV6_FIB_RULES :
entry = & fib_data - > ipv6 . rules ;
break ;
default :
return 0 ;
}
return max ? entry - > max : entry - > num ;
}
2019-10-03 11:49:36 +02:00
static void nsim_fib_set_max ( struct nsim_fib_data * fib_data ,
enum nsim_resource_id res_id , u64 val )
2018-03-27 18:22:00 -07:00
{
struct nsim_fib_entry * entry ;
switch ( res_id ) {
case NSIM_RESOURCE_IPV4_FIB :
entry = & fib_data - > ipv4 . fib ;
break ;
case NSIM_RESOURCE_IPV4_FIB_RULES :
entry = & fib_data - > ipv4 . rules ;
break ;
case NSIM_RESOURCE_IPV6_FIB :
entry = & fib_data - > ipv6 . fib ;
break ;
case NSIM_RESOURCE_IPV6_FIB_RULES :
entry = & fib_data - > ipv6 . rules ;
break ;
default :
2019-10-03 11:49:36 +02:00
WARN_ON ( 1 ) ;
return ;
2018-06-05 08:14:10 -07:00
}
2019-10-03 11:49:36 +02:00
entry - > max = val ;
2018-03-27 18:22:00 -07:00
}
static int nsim_fib_rule_account ( struct nsim_fib_entry * entry , bool add ,
struct netlink_ext_ack * extack )
{
int err = 0 ;
if ( add ) {
if ( entry - > num < entry - > max ) {
entry - > num + + ;
} else {
err = - ENOSPC ;
NL_SET_ERR_MSG_MOD ( extack , " Exceeded number of supported fib rule entries " ) ;
}
} else {
entry - > num - - ;
}
return err ;
}
2019-10-03 11:49:26 +02:00
static int nsim_fib_rule_event ( struct nsim_fib_data * data ,
struct fib_notifier_info * info , bool add )
2018-03-27 18:22:00 -07:00
{
struct netlink_ext_ack * extack = info - > extack ;
int err = 0 ;
switch ( info - > family ) {
case AF_INET :
err = nsim_fib_rule_account ( & data - > ipv4 . rules , add , extack ) ;
break ;
case AF_INET6 :
err = nsim_fib_rule_account ( & data - > ipv6 . rules , add , extack ) ;
break ;
}
return err ;
}
static int nsim_fib_account ( struct nsim_fib_entry * entry , bool add ,
struct netlink_ext_ack * extack )
{
int err = 0 ;
if ( add ) {
if ( entry - > num < entry - > max ) {
entry - > num + + ;
} else {
err = - ENOSPC ;
NL_SET_ERR_MSG_MOD ( extack , " Exceeded number of supported fib entries " ) ;
}
} else {
entry - > num - - ;
}
return err ;
}
2020-01-14 13:23:15 +02:00
static void nsim_fib_rt_init ( struct nsim_fib_data * data ,
struct nsim_fib_rt * fib_rt , const void * addr ,
size_t addr_len , unsigned int prefix_len ,
int family , u32 tb_id )
{
memcpy ( fib_rt - > key . addr , addr , addr_len ) ;
fib_rt - > key . prefix_len = prefix_len ;
fib_rt - > key . family = family ;
fib_rt - > key . tb_id = tb_id ;
list_add ( & fib_rt - > list , & data - > fib_rt_list ) ;
}
static void nsim_fib_rt_fini ( struct nsim_fib_rt * fib_rt )
{
list_del ( & fib_rt - > list ) ;
}
static struct nsim_fib_rt * nsim_fib_rt_lookup ( struct rhashtable * fib_rt_ht ,
const void * addr , size_t addr_len ,
unsigned int prefix_len ,
int family , u32 tb_id )
{
struct nsim_fib_rt_key key ;
memset ( & key , 0 , sizeof ( key ) ) ;
memcpy ( key . addr , addr , addr_len ) ;
key . prefix_len = prefix_len ;
key . family = family ;
key . tb_id = tb_id ;
return rhashtable_lookup_fast ( fib_rt_ht , & key , nsim_fib_rt_ht_params ) ;
}
static struct nsim_fib4_rt *
nsim_fib4_rt_create ( struct nsim_fib_data * data ,
struct fib_entry_notifier_info * fen_info )
{
struct nsim_fib4_rt * fib4_rt ;
fib4_rt = kzalloc ( sizeof ( * fib4_rt ) , GFP_ATOMIC ) ;
if ( ! fib4_rt )
return NULL ;
nsim_fib_rt_init ( data , & fib4_rt - > common , & fen_info - > dst , sizeof ( u32 ) ,
fen_info - > dst_len , AF_INET , fen_info - > tb_id ) ;
fib4_rt - > fi = fen_info - > fi ;
fib_info_hold ( fib4_rt - > fi ) ;
fib4_rt - > tos = fen_info - > tos ;
fib4_rt - > type = fen_info - > type ;
return fib4_rt ;
}
static void nsim_fib4_rt_destroy ( struct nsim_fib4_rt * fib4_rt )
{
fib_info_put ( fib4_rt - > fi ) ;
nsim_fib_rt_fini ( & fib4_rt - > common ) ;
kfree ( fib4_rt ) ;
}
static struct nsim_fib4_rt *
nsim_fib4_rt_lookup ( struct rhashtable * fib_rt_ht ,
const struct fib_entry_notifier_info * fen_info )
{
struct nsim_fib_rt * fib_rt ;
fib_rt = nsim_fib_rt_lookup ( fib_rt_ht , & fen_info - > dst , sizeof ( u32 ) ,
fen_info - > dst_len , AF_INET ,
fen_info - > tb_id ) ;
if ( ! fib_rt )
return NULL ;
return container_of ( fib_rt , struct nsim_fib4_rt , common ) ;
}
static void nsim_fib4_rt_hw_flags_set ( struct net * net ,
const struct nsim_fib4_rt * fib4_rt ,
bool trap )
{
u32 * p_dst = ( u32 * ) fib4_rt - > common . key . addr ;
int dst_len = fib4_rt - > common . key . prefix_len ;
struct fib_rt_info fri ;
fri . fi = fib4_rt - > fi ;
fri . tb_id = fib4_rt - > common . key . tb_id ;
fri . dst = cpu_to_be32 ( * p_dst ) ;
fri . dst_len = dst_len ;
fri . tos = fib4_rt - > tos ;
fri . type = fib4_rt - > type ;
fri . offload = false ;
fri . trap = trap ;
fib_alias_hw_flags_set ( net , & fri ) ;
}
static int nsim_fib4_rt_add ( struct nsim_fib_data * data ,
struct nsim_fib4_rt * fib4_rt ,
struct netlink_ext_ack * extack )
{
struct net * net = devlink_net ( data - > devlink ) ;
int err ;
err = nsim_fib_account ( & data - > ipv4 . fib , true , extack ) ;
if ( err )
return err ;
err = rhashtable_insert_fast ( & data - > fib_rt_ht ,
& fib4_rt - > common . ht_node ,
nsim_fib_rt_ht_params ) ;
if ( err ) {
NL_SET_ERR_MSG_MOD ( extack , " Failed to insert IPv4 route " ) ;
goto err_fib_dismiss ;
}
nsim_fib4_rt_hw_flags_set ( net , fib4_rt , true ) ;
return 0 ;
err_fib_dismiss :
nsim_fib_account ( & data - > ipv4 . fib , false , extack ) ;
return err ;
}
static int nsim_fib4_rt_replace ( struct nsim_fib_data * data ,
struct nsim_fib4_rt * fib4_rt ,
struct nsim_fib4_rt * fib4_rt_old ,
struct netlink_ext_ack * extack )
{
struct net * net = devlink_net ( data - > devlink ) ;
int err ;
/* We are replacing a route, so no need to change the accounting. */
err = rhashtable_replace_fast ( & data - > fib_rt_ht ,
& fib4_rt_old - > common . ht_node ,
& fib4_rt - > common . ht_node ,
nsim_fib_rt_ht_params ) ;
if ( err ) {
NL_SET_ERR_MSG_MOD ( extack , " Failed to replace IPv4 route " ) ;
return err ;
}
nsim_fib4_rt_hw_flags_set ( net , fib4_rt , true ) ;
nsim_fib4_rt_hw_flags_set ( net , fib4_rt_old , false ) ;
nsim_fib4_rt_destroy ( fib4_rt_old ) ;
return 0 ;
}
static int nsim_fib4_rt_insert ( struct nsim_fib_data * data ,
struct fib_entry_notifier_info * fen_info )
{
struct netlink_ext_ack * extack = fen_info - > info . extack ;
struct nsim_fib4_rt * fib4_rt , * fib4_rt_old ;
int err ;
fib4_rt = nsim_fib4_rt_create ( data , fen_info ) ;
if ( ! fib4_rt )
return - ENOMEM ;
fib4_rt_old = nsim_fib4_rt_lookup ( & data - > fib_rt_ht , fen_info ) ;
if ( ! fib4_rt_old )
err = nsim_fib4_rt_add ( data , fib4_rt , extack ) ;
else
err = nsim_fib4_rt_replace ( data , fib4_rt , fib4_rt_old , extack ) ;
if ( err )
nsim_fib4_rt_destroy ( fib4_rt ) ;
return err ;
}
static void nsim_fib4_rt_remove ( struct nsim_fib_data * data ,
const struct fib_entry_notifier_info * fen_info )
{
struct netlink_ext_ack * extack = fen_info - > info . extack ;
struct nsim_fib4_rt * fib4_rt ;
fib4_rt = nsim_fib4_rt_lookup ( & data - > fib_rt_ht , fen_info ) ;
if ( WARN_ON_ONCE ( ! fib4_rt ) )
return ;
rhashtable_remove_fast ( & data - > fib_rt_ht , & fib4_rt - > common . ht_node ,
nsim_fib_rt_ht_params ) ;
nsim_fib_account ( & data - > ipv4 . fib , false , extack ) ;
nsim_fib4_rt_destroy ( fib4_rt ) ;
}
static int nsim_fib4_event ( struct nsim_fib_data * data ,
struct fib_notifier_info * info ,
unsigned long event )
{
struct fib_entry_notifier_info * fen_info ;
int err = 0 ;
fen_info = container_of ( info , struct fib_entry_notifier_info , info ) ;
if ( fen_info - > fi - > nh ) {
NL_SET_ERR_MSG_MOD ( info - > extack , " IPv4 route with nexthop objects is not supported " ) ;
return 0 ;
}
switch ( event ) {
case FIB_EVENT_ENTRY_REPLACE :
err = nsim_fib4_rt_insert ( data , fen_info ) ;
break ;
case FIB_EVENT_ENTRY_DEL :
nsim_fib4_rt_remove ( data , fen_info ) ;
break ;
default :
break ;
}
return err ;
}
static struct nsim_fib6_rt_nh *
nsim_fib6_rt_nh_find ( const struct nsim_fib6_rt * fib6_rt ,
const struct fib6_info * rt )
{
struct nsim_fib6_rt_nh * fib6_rt_nh ;
list_for_each_entry ( fib6_rt_nh , & fib6_rt - > nh_list , list ) {
if ( fib6_rt_nh - > rt = = rt )
return fib6_rt_nh ;
}
return NULL ;
}
static int nsim_fib6_rt_nh_add ( struct nsim_fib6_rt * fib6_rt ,
struct fib6_info * rt )
{
struct nsim_fib6_rt_nh * fib6_rt_nh ;
fib6_rt_nh = kzalloc ( sizeof ( * fib6_rt_nh ) , GFP_ATOMIC ) ;
if ( ! fib6_rt_nh )
return - ENOMEM ;
fib6_info_hold ( rt ) ;
fib6_rt_nh - > rt = rt ;
list_add_tail ( & fib6_rt_nh - > list , & fib6_rt - > nh_list ) ;
fib6_rt - > nhs + + ;
return 0 ;
}
static void nsim_fib6_rt_nh_del ( struct nsim_fib6_rt * fib6_rt ,
const struct fib6_info * rt )
{
struct nsim_fib6_rt_nh * fib6_rt_nh ;
fib6_rt_nh = nsim_fib6_rt_nh_find ( fib6_rt , rt ) ;
if ( WARN_ON_ONCE ( ! fib6_rt_nh ) )
return ;
fib6_rt - > nhs - - ;
list_del ( & fib6_rt_nh - > list ) ;
# if IS_ENABLED(CONFIG_IPV6)
fib6_info_release ( fib6_rt_nh - > rt ) ;
# endif
kfree ( fib6_rt_nh ) ;
}
static struct nsim_fib6_rt *
nsim_fib6_rt_create ( struct nsim_fib_data * data ,
struct fib6_entry_notifier_info * fen6_info )
{
struct fib6_info * iter , * rt = fen6_info - > rt ;
struct nsim_fib6_rt * fib6_rt ;
int i = 0 ;
int err ;
fib6_rt = kzalloc ( sizeof ( * fib6_rt ) , GFP_ATOMIC ) ;
if ( ! fib6_rt )
2020-01-15 11:57:41 -08:00
return ERR_PTR ( - ENOMEM ) ;
2020-01-14 13:23:15 +02:00
nsim_fib_rt_init ( data , & fib6_rt - > common , & rt - > fib6_dst . addr ,
sizeof ( rt - > fib6_dst . addr ) , rt - > fib6_dst . plen , AF_INET6 ,
rt - > fib6_table - > tb6_id ) ;
/* We consider a multipath IPv6 route as one entry, but it can be made
* up from several fib6_info structs ( one for each nexthop ) , so we
* add them all to the same list under the entry .
*/
INIT_LIST_HEAD ( & fib6_rt - > nh_list ) ;
err = nsim_fib6_rt_nh_add ( fib6_rt , rt ) ;
if ( err )
goto err_fib_rt_fini ;
if ( ! fen6_info - > nsiblings )
return fib6_rt ;
list_for_each_entry ( iter , & rt - > fib6_siblings , fib6_siblings ) {
if ( i = = fen6_info - > nsiblings )
break ;
err = nsim_fib6_rt_nh_add ( fib6_rt , iter ) ;
if ( err )
goto err_fib6_rt_nh_del ;
i + + ;
}
WARN_ON_ONCE ( i ! = fen6_info - > nsiblings ) ;
return fib6_rt ;
err_fib6_rt_nh_del :
list_for_each_entry_continue_reverse ( iter , & rt - > fib6_siblings ,
fib6_siblings )
nsim_fib6_rt_nh_del ( fib6_rt , iter ) ;
nsim_fib6_rt_nh_del ( fib6_rt , rt ) ;
err_fib_rt_fini :
nsim_fib_rt_fini ( & fib6_rt - > common ) ;
kfree ( fib6_rt ) ;
return ERR_PTR ( err ) ;
}
static void nsim_fib6_rt_destroy ( struct nsim_fib6_rt * fib6_rt )
{
struct nsim_fib6_rt_nh * iter , * tmp ;
list_for_each_entry_safe ( iter , tmp , & fib6_rt - > nh_list , list )
nsim_fib6_rt_nh_del ( fib6_rt , iter - > rt ) ;
WARN_ON_ONCE ( ! list_empty ( & fib6_rt - > nh_list ) ) ;
nsim_fib_rt_fini ( & fib6_rt - > common ) ;
kfree ( fib6_rt ) ;
}
static struct nsim_fib6_rt *
nsim_fib6_rt_lookup ( struct rhashtable * fib_rt_ht , const struct fib6_info * rt )
{
struct nsim_fib_rt * fib_rt ;
fib_rt = nsim_fib_rt_lookup ( fib_rt_ht , & rt - > fib6_dst . addr ,
sizeof ( rt - > fib6_dst . addr ) ,
rt - > fib6_dst . plen , AF_INET6 ,
rt - > fib6_table - > tb6_id ) ;
if ( ! fib_rt )
return NULL ;
return container_of ( fib_rt , struct nsim_fib6_rt , common ) ;
}
static int nsim_fib6_rt_append ( struct nsim_fib_data * data ,
struct fib6_entry_notifier_info * fen6_info )
{
struct fib6_info * iter , * rt = fen6_info - > rt ;
struct nsim_fib6_rt * fib6_rt ;
int i = 0 ;
int err ;
fib6_rt = nsim_fib6_rt_lookup ( & data - > fib_rt_ht , rt ) ;
if ( WARN_ON_ONCE ( ! fib6_rt ) )
return - EINVAL ;
err = nsim_fib6_rt_nh_add ( fib6_rt , rt ) ;
if ( err )
return err ;
rt - > trap = true ;
if ( ! fen6_info - > nsiblings )
return 0 ;
list_for_each_entry ( iter , & rt - > fib6_siblings , fib6_siblings ) {
if ( i = = fen6_info - > nsiblings )
break ;
err = nsim_fib6_rt_nh_add ( fib6_rt , iter ) ;
if ( err )
goto err_fib6_rt_nh_del ;
iter - > trap = true ;
i + + ;
}
WARN_ON_ONCE ( i ! = fen6_info - > nsiblings ) ;
return 0 ;
err_fib6_rt_nh_del :
list_for_each_entry_continue_reverse ( iter , & rt - > fib6_siblings ,
fib6_siblings ) {
iter - > trap = false ;
nsim_fib6_rt_nh_del ( fib6_rt , iter ) ;
}
rt - > trap = false ;
nsim_fib6_rt_nh_del ( fib6_rt , rt ) ;
return err ;
}
static void nsim_fib6_rt_hw_flags_set ( const struct nsim_fib6_rt * fib6_rt ,
bool trap )
{
struct nsim_fib6_rt_nh * fib6_rt_nh ;
list_for_each_entry ( fib6_rt_nh , & fib6_rt - > nh_list , list )
fib6_info_hw_flags_set ( fib6_rt_nh - > rt , false , trap ) ;
}
static int nsim_fib6_rt_add ( struct nsim_fib_data * data ,
struct nsim_fib6_rt * fib6_rt ,
struct netlink_ext_ack * extack )
{
int err ;
err = nsim_fib_account ( & data - > ipv6 . fib , true , extack ) ;
if ( err )
return err ;
err = rhashtable_insert_fast ( & data - > fib_rt_ht ,
& fib6_rt - > common . ht_node ,
nsim_fib_rt_ht_params ) ;
if ( err ) {
NL_SET_ERR_MSG_MOD ( extack , " Failed to insert IPv6 route " ) ;
goto err_fib_dismiss ;
}
nsim_fib6_rt_hw_flags_set ( fib6_rt , true ) ;
return 0 ;
err_fib_dismiss :
nsim_fib_account ( & data - > ipv6 . fib , false , extack ) ;
return err ;
}
static int nsim_fib6_rt_replace ( struct nsim_fib_data * data ,
struct nsim_fib6_rt * fib6_rt ,
struct nsim_fib6_rt * fib6_rt_old ,
struct netlink_ext_ack * extack )
{
int err ;
/* We are replacing a route, so no need to change the accounting. */
err = rhashtable_replace_fast ( & data - > fib_rt_ht ,
& fib6_rt_old - > common . ht_node ,
& fib6_rt - > common . ht_node ,
nsim_fib_rt_ht_params ) ;
if ( err ) {
NL_SET_ERR_MSG_MOD ( extack , " Failed to replace IPv6 route " ) ;
return err ;
}
nsim_fib6_rt_hw_flags_set ( fib6_rt , true ) ;
nsim_fib6_rt_hw_flags_set ( fib6_rt_old , false ) ;
nsim_fib6_rt_destroy ( fib6_rt_old ) ;
return 0 ;
}
static int nsim_fib6_rt_insert ( struct nsim_fib_data * data ,
struct fib6_entry_notifier_info * fen6_info )
{
struct netlink_ext_ack * extack = fen6_info - > info . extack ;
struct nsim_fib6_rt * fib6_rt , * fib6_rt_old ;
int err ;
fib6_rt = nsim_fib6_rt_create ( data , fen6_info ) ;
2020-01-15 11:57:41 -08:00
if ( IS_ERR ( fib6_rt ) )
return PTR_ERR ( fib6_rt ) ;
2020-01-14 13:23:15 +02:00
fib6_rt_old = nsim_fib6_rt_lookup ( & data - > fib_rt_ht , fen6_info - > rt ) ;
if ( ! fib6_rt_old )
err = nsim_fib6_rt_add ( data , fib6_rt , extack ) ;
else
err = nsim_fib6_rt_replace ( data , fib6_rt , fib6_rt_old , extack ) ;
if ( err )
nsim_fib6_rt_destroy ( fib6_rt ) ;
return err ;
}
static void
nsim_fib6_rt_remove ( struct nsim_fib_data * data ,
const struct fib6_entry_notifier_info * fen6_info )
{
struct netlink_ext_ack * extack = fen6_info - > info . extack ;
struct nsim_fib6_rt * fib6_rt ;
/* Multipath routes are first added to the FIB trie and only then
* notified . If we vetoed the addition , we will get a delete
* notification for a route we do not have . Therefore , do not warn if
* route was not found .
*/
fib6_rt = nsim_fib6_rt_lookup ( & data - > fib_rt_ht , fen6_info - > rt ) ;
if ( ! fib6_rt )
return ;
/* If not all the nexthops are deleted, then only reduce the nexthop
* group .
*/
if ( fen6_info - > nsiblings + 1 ! = fib6_rt - > nhs ) {
nsim_fib6_rt_nh_del ( fib6_rt , fen6_info - > rt ) ;
return ;
}
rhashtable_remove_fast ( & data - > fib_rt_ht , & fib6_rt - > common . ht_node ,
nsim_fib_rt_ht_params ) ;
nsim_fib_account ( & data - > ipv6 . fib , false , extack ) ;
nsim_fib6_rt_destroy ( fib6_rt ) ;
}
static int nsim_fib6_event ( struct nsim_fib_data * data ,
struct fib_notifier_info * info ,
unsigned long event )
{
struct fib6_entry_notifier_info * fen6_info ;
int err = 0 ;
fen6_info = container_of ( info , struct fib6_entry_notifier_info , info ) ;
if ( fen6_info - > rt - > nh ) {
NL_SET_ERR_MSG_MOD ( info - > extack , " IPv6 route with nexthop objects is not supported " ) ;
return 0 ;
}
if ( fen6_info - > rt - > fib6_src . plen ) {
NL_SET_ERR_MSG_MOD ( info - > extack , " IPv6 source-specific route is not supported " ) ;
return 0 ;
}
switch ( event ) {
case FIB_EVENT_ENTRY_REPLACE :
err = nsim_fib6_rt_insert ( data , fen6_info ) ;
break ;
case FIB_EVENT_ENTRY_APPEND :
err = nsim_fib6_rt_append ( data , fen6_info ) ;
break ;
case FIB_EVENT_ENTRY_DEL :
nsim_fib6_rt_remove ( data , fen6_info ) ;
break ;
default :
break ;
}
return err ;
}
2019-10-03 11:49:26 +02:00
static int nsim_fib_event ( struct nsim_fib_data * data ,
2020-01-14 13:23:15 +02:00
struct fib_notifier_info * info , unsigned long event )
2018-03-27 18:22:00 -07:00
{
int err = 0 ;
switch ( info - > family ) {
case AF_INET :
2020-01-14 13:23:15 +02:00
err = nsim_fib4_event ( data , info , event ) ;
2018-03-27 18:22:00 -07:00
break ;
case AF_INET6 :
2020-01-14 13:23:15 +02:00
err = nsim_fib6_event ( data , info , event ) ;
2018-03-27 18:22:00 -07:00
break ;
}
return err ;
}
static int nsim_fib_event_nb ( struct notifier_block * nb , unsigned long event ,
void * ptr )
{
2019-10-03 11:49:26 +02:00
struct nsim_fib_data * data = container_of ( nb , struct nsim_fib_data ,
fib_nb ) ;
2018-03-27 18:22:00 -07:00
struct fib_notifier_info * info = ptr ;
int err = 0 ;
2020-01-14 13:23:15 +02:00
/* IPv6 routes can be added via RAs from softIRQ. */
spin_lock_bh ( & data - > fib_lock ) ;
2018-03-27 18:22:00 -07:00
switch ( event ) {
2020-08-23 17:36:59 -05:00
case FIB_EVENT_RULE_ADD :
2018-03-27 18:22:00 -07:00
case FIB_EVENT_RULE_DEL :
2019-10-03 11:49:26 +02:00
err = nsim_fib_rule_event ( data , info ,
event = = FIB_EVENT_RULE_ADD ) ;
2018-03-27 18:22:00 -07:00
break ;
2020-08-23 17:36:59 -05:00
case FIB_EVENT_ENTRY_REPLACE :
case FIB_EVENT_ENTRY_APPEND :
2018-03-27 18:22:00 -07:00
case FIB_EVENT_ENTRY_DEL :
2020-01-14 13:23:15 +02:00
err = nsim_fib_event ( data , info , event ) ;
2018-03-27 18:22:00 -07:00
break ;
}
2020-01-14 13:23:15 +02:00
spin_unlock_bh ( & data - > fib_lock ) ;
2018-03-27 18:22:00 -07:00
return notifier_from_errno ( err ) ;
}
2020-01-14 13:23:15 +02:00
static void nsim_fib4_rt_free ( struct nsim_fib_rt * fib_rt ,
struct nsim_fib_data * data )
{
struct devlink * devlink = data - > devlink ;
struct nsim_fib4_rt * fib4_rt ;
fib4_rt = container_of ( fib_rt , struct nsim_fib4_rt , common ) ;
nsim_fib4_rt_hw_flags_set ( devlink_net ( devlink ) , fib4_rt , false ) ;
nsim_fib_account ( & data - > ipv4 . fib , false , NULL ) ;
nsim_fib4_rt_destroy ( fib4_rt ) ;
}
static void nsim_fib6_rt_free ( struct nsim_fib_rt * fib_rt ,
struct nsim_fib_data * data )
{
struct nsim_fib6_rt * fib6_rt ;
fib6_rt = container_of ( fib_rt , struct nsim_fib6_rt , common ) ;
nsim_fib6_rt_hw_flags_set ( fib6_rt , false ) ;
nsim_fib_account ( & data - > ipv6 . fib , false , NULL ) ;
nsim_fib6_rt_destroy ( fib6_rt ) ;
}
static void nsim_fib_rt_free ( void * ptr , void * arg )
{
struct nsim_fib_rt * fib_rt = ptr ;
struct nsim_fib_data * data = arg ;
switch ( fib_rt - > key . family ) {
case AF_INET :
nsim_fib4_rt_free ( fib_rt , data ) ;
break ;
case AF_INET6 :
nsim_fib6_rt_free ( fib_rt , data ) ;
break ;
default :
WARN_ON_ONCE ( 1 ) ;
}
}
2018-03-27 18:22:00 -07:00
/* inconsistent dump, trying again */
static void nsim_fib_dump_inconsistent ( struct notifier_block * nb )
{
2019-10-03 11:49:26 +02:00
struct nsim_fib_data * data = container_of ( nb , struct nsim_fib_data ,
fib_nb ) ;
2020-01-14 13:23:15 +02:00
struct nsim_fib_rt * fib_rt , * fib_rt_tmp ;
/* The notifier block is still not registered, so we do not need to
* take any locks here .
*/
list_for_each_entry_safe ( fib_rt , fib_rt_tmp , & data - > fib_rt_list , list ) {
rhashtable_remove_fast ( & data - > fib_rt_ht , & fib_rt - > ht_node ,
nsim_fib_rt_ht_params ) ;
nsim_fib_rt_free ( fib_rt , data ) ;
}
2019-08-06 12:15:17 -07:00
2019-10-03 11:49:26 +02:00
data - > ipv4 . rules . num = 0ULL ;
data - > ipv6 . rules . num = 0ULL ;
}
2019-08-06 12:15:17 -07:00
2019-10-03 11:49:26 +02:00
static u64 nsim_fib_ipv4_resource_occ_get ( void * priv )
{
struct nsim_fib_data * data = priv ;
2018-03-27 18:22:00 -07:00
2019-10-03 11:49:26 +02:00
return nsim_fib_get_val ( data , NSIM_RESOURCE_IPV4_FIB , false ) ;
2018-03-27 18:22:00 -07:00
}
2019-10-03 11:49:26 +02:00
static u64 nsim_fib_ipv4_rules_res_occ_get ( void * priv )
2019-08-06 12:15:17 -07:00
{
2019-10-03 11:49:26 +02:00
struct nsim_fib_data * data = priv ;
2018-03-27 18:22:00 -07:00
2019-10-03 11:49:26 +02:00
return nsim_fib_get_val ( data , NSIM_RESOURCE_IPV4_FIB_RULES , false ) ;
}
2018-03-27 18:22:00 -07:00
2019-10-03 11:49:26 +02:00
static u64 nsim_fib_ipv6_resource_occ_get ( void * priv )
{
struct nsim_fib_data * data = priv ;
2018-03-27 18:22:00 -07:00
2019-10-03 11:49:26 +02:00
return nsim_fib_get_val ( data , NSIM_RESOURCE_IPV6_FIB , false ) ;
2019-08-06 12:15:17 -07:00
}
2018-03-27 18:22:00 -07:00
2019-10-03 11:49:26 +02:00
static u64 nsim_fib_ipv6_rules_res_occ_get ( void * priv )
2019-08-06 12:15:17 -07:00
{
2019-10-03 11:49:26 +02:00
struct nsim_fib_data * data = priv ;
return nsim_fib_get_val ( data , NSIM_RESOURCE_IPV6_FIB_RULES , false ) ;
2019-04-25 15:59:42 +02:00
}
2019-10-03 11:49:36 +02:00
static void nsim_fib_set_max_all ( struct nsim_fib_data * data ,
struct devlink * devlink )
{
enum nsim_resource_id res_ids [ ] = {
NSIM_RESOURCE_IPV4_FIB , NSIM_RESOURCE_IPV4_FIB_RULES ,
NSIM_RESOURCE_IPV6_FIB , NSIM_RESOURCE_IPV6_FIB_RULES
} ;
int i ;
for ( i = 0 ; i < ARRAY_SIZE ( res_ids ) ; i + + ) {
int err ;
u64 val ;
err = devlink_resource_size_get ( devlink , res_ids [ i ] , & val ) ;
if ( err )
val = ( u64 ) - 1 ;
nsim_fib_set_max ( data , res_ids [ i ] , val ) ;
}
}
struct nsim_fib_data * nsim_fib_create ( struct devlink * devlink ,
struct netlink_ext_ack * extack )
2019-04-25 15:59:42 +02:00
{
2019-10-03 11:49:26 +02:00
struct nsim_fib_data * data ;
2019-08-06 12:15:17 -07:00
int err ;
2019-10-03 11:49:26 +02:00
data = kzalloc ( sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return ERR_PTR ( - ENOMEM ) ;
2020-01-14 13:23:15 +02:00
data - > devlink = devlink ;
spin_lock_init ( & data - > fib_lock ) ;
INIT_LIST_HEAD ( & data - > fib_rt_list ) ;
err = rhashtable_init ( & data - > fib_rt_ht , & nsim_fib_rt_ht_params ) ;
if ( err )
goto err_data_free ;
2019-08-06 12:15:17 -07:00
2019-10-03 11:49:36 +02:00
nsim_fib_set_max_all ( data , devlink ) ;
2019-10-03 11:49:26 +02:00
data - > fib_nb . notifier_call = nsim_fib_event_nb ;
2019-10-03 11:49:38 +02:00
err = register_fib_notifier ( devlink_net ( devlink ) , & data - > fib_nb ,
2019-10-03 11:49:36 +02:00
nsim_fib_dump_inconsistent , extack ) ;
2019-10-03 11:49:26 +02:00
if ( err ) {
2019-08-06 12:15:17 -07:00
pr_err ( " Failed to register fib notifier \n " ) ;
2020-01-14 13:23:15 +02:00
goto err_rhashtable_destroy ;
2019-08-06 12:15:17 -07:00
}
2019-10-03 11:49:26 +02:00
devlink_resource_occ_get_register ( devlink ,
NSIM_RESOURCE_IPV4_FIB ,
nsim_fib_ipv4_resource_occ_get ,
data ) ;
devlink_resource_occ_get_register ( devlink ,
NSIM_RESOURCE_IPV4_FIB_RULES ,
nsim_fib_ipv4_rules_res_occ_get ,
data ) ;
devlink_resource_occ_get_register ( devlink ,
NSIM_RESOURCE_IPV6_FIB ,
nsim_fib_ipv6_resource_occ_get ,
data ) ;
devlink_resource_occ_get_register ( devlink ,
NSIM_RESOURCE_IPV6_FIB_RULES ,
nsim_fib_ipv6_rules_res_occ_get ,
data ) ;
return data ;
2020-01-14 13:23:15 +02:00
err_rhashtable_destroy :
rhashtable_free_and_destroy ( & data - > fib_rt_ht , nsim_fib_rt_free ,
data ) ;
err_data_free :
2019-10-03 11:49:26 +02:00
kfree ( data ) ;
return ERR_PTR ( err ) ;
}
void nsim_fib_destroy ( struct devlink * devlink , struct nsim_fib_data * data )
{
devlink_resource_occ_get_unregister ( devlink ,
NSIM_RESOURCE_IPV6_FIB_RULES ) ;
devlink_resource_occ_get_unregister ( devlink ,
NSIM_RESOURCE_IPV6_FIB ) ;
devlink_resource_occ_get_unregister ( devlink ,
NSIM_RESOURCE_IPV4_FIB_RULES ) ;
devlink_resource_occ_get_unregister ( devlink ,
NSIM_RESOURCE_IPV4_FIB ) ;
2019-10-03 11:49:38 +02:00
unregister_fib_notifier ( devlink_net ( devlink ) , & data - > fib_nb ) ;
2020-01-14 13:23:15 +02:00
rhashtable_free_and_destroy ( & data - > fib_rt_ht , nsim_fib_rt_free ,
data ) ;
WARN_ON_ONCE ( ! list_empty ( & data - > fib_rt_list ) ) ;
2019-10-03 11:49:26 +02:00
kfree ( data ) ;
2018-03-27 18:22:00 -07:00
}