2018-03-01 00:29:29 +03:00
/* Linux multicast routing support
* Common logic shared by IPv4 [ ipmr ] and IPv6 [ ip6mr ] implementation
*/
2018-06-18 05:52:50 +03:00
# include <linux/rhashtable.h>
2018-03-01 00:29:29 +03:00
# include <linux/mroute_base.h>
/* Sets everything common except 'dev', since that is done under locking */
void vif_device_init ( struct vif_device * v ,
struct net_device * dev ,
unsigned long rate_limit ,
unsigned char threshold ,
unsigned short flags ,
unsigned short get_iflink_mask )
{
v - > dev = NULL ;
v - > bytes_in = 0 ;
v - > bytes_out = 0 ;
v - > pkt_in = 0 ;
v - > pkt_out = 0 ;
v - > rate_limit = rate_limit ;
v - > flags = flags ;
v - > threshold = threshold ;
if ( v - > flags & get_iflink_mask )
v - > link = dev_get_iflink ( dev ) ;
else
v - > link = dev - > ifindex ;
}
EXPORT_SYMBOL ( vif_device_init ) ;
2018-03-01 00:29:33 +03:00
struct mr_table *
mr_table_alloc ( struct net * net , u32 id ,
2018-03-01 00:29:35 +03:00
struct mr_table_ops * ops ,
2018-03-01 00:29:33 +03:00
void ( * expire_func ) ( struct timer_list * t ) ,
void ( * table_set ) ( struct mr_table * mrt ,
struct net * net ) )
{
struct mr_table * mrt ;
2018-06-05 16:02:00 +03:00
int err ;
2018-03-01 00:29:33 +03:00
mrt = kzalloc ( sizeof ( * mrt ) , GFP_KERNEL ) ;
if ( ! mrt )
2018-06-05 16:02:00 +03:00
return ERR_PTR ( - ENOMEM ) ;
2018-03-01 00:29:33 +03:00
mrt - > id = id ;
write_pnet ( & mrt - > net , net ) ;
2018-03-01 00:29:35 +03:00
mrt - > ops = * ops ;
2018-06-05 16:02:00 +03:00
err = rhltable_init ( & mrt - > mfc_hash , mrt - > ops . rht_params ) ;
if ( err ) {
2018-05-21 20:51:53 +03:00
kfree ( mrt ) ;
2018-06-05 16:02:00 +03:00
return ERR_PTR ( err ) ;
2018-05-21 20:51:53 +03:00
}
2018-03-01 00:29:33 +03:00
INIT_LIST_HEAD ( & mrt - > mfc_cache_list ) ;
INIT_LIST_HEAD ( & mrt - > mfc_unres_queue ) ;
timer_setup ( & mrt - > ipmr_expire_timer , expire_func , 0 ) ;
mrt - > mroute_reg_vif_num = - 1 ;
table_set ( mrt , net ) ;
return mrt ;
}
EXPORT_SYMBOL ( mr_table_alloc ) ;
2018-03-01 00:29:35 +03:00
void * mr_mfc_find_parent ( struct mr_table * mrt , void * hasharg , int parent )
{
struct rhlist_head * tmp , * list ;
struct mr_mfc * c ;
list = rhltable_lookup ( & mrt - > mfc_hash , hasharg , * mrt - > ops . rht_params ) ;
rhl_for_each_entry_rcu ( c , tmp , list , mnode )
if ( parent = = - 1 | | parent = = c - > mfc_parent )
return c ;
return NULL ;
}
EXPORT_SYMBOL ( mr_mfc_find_parent ) ;
void * mr_mfc_find_any_parent ( struct mr_table * mrt , int vifi )
{
struct rhlist_head * tmp , * list ;
struct mr_mfc * c ;
list = rhltable_lookup ( & mrt - > mfc_hash , mrt - > ops . cmparg_any ,
* mrt - > ops . rht_params ) ;
rhl_for_each_entry_rcu ( c , tmp , list , mnode )
if ( c - > mfc_un . res . ttls [ vifi ] < 255 )
return c ;
return NULL ;
}
EXPORT_SYMBOL ( mr_mfc_find_any_parent ) ;
void * mr_mfc_find_any ( struct mr_table * mrt , int vifi , void * hasharg )
{
struct rhlist_head * tmp , * list ;
struct mr_mfc * c , * proxy ;
list = rhltable_lookup ( & mrt - > mfc_hash , hasharg , * mrt - > ops . rht_params ) ;
rhl_for_each_entry_rcu ( c , tmp , list , mnode ) {
if ( c - > mfc_un . res . ttls [ vifi ] < 255 )
return c ;
/* It's ok if the vifi is part of the static tree */
proxy = mr_mfc_find_any_parent ( mrt , c - > mfc_parent ) ;
if ( proxy & & proxy - > mfc_un . res . ttls [ vifi ] < 255 )
return c ;
}
return mr_mfc_find_any_parent ( mrt , vifi ) ;
}
EXPORT_SYMBOL ( mr_mfc_find_any ) ;
2018-03-01 00:29:36 +03:00
# ifdef CONFIG_PROC_FS
2018-03-01 00:29:37 +03:00
void * mr_vif_seq_idx ( struct net * net , struct mr_vif_iter * iter , loff_t pos )
{
struct mr_table * mrt = iter - > mrt ;
for ( iter - > ct = 0 ; iter - > ct < mrt - > maxvif ; + + iter - > ct ) {
if ( ! VIF_EXISTS ( mrt , iter - > ct ) )
continue ;
if ( pos - - = = 0 )
return & mrt - > vif_table [ iter - > ct ] ;
}
return NULL ;
}
EXPORT_SYMBOL ( mr_vif_seq_idx ) ;
void * mr_vif_seq_next ( struct seq_file * seq , void * v , loff_t * pos )
{
struct mr_vif_iter * iter = seq - > private ;
struct net * net = seq_file_net ( seq ) ;
struct mr_table * mrt = iter - > mrt ;
+ + * pos ;
if ( v = = SEQ_START_TOKEN )
return mr_vif_seq_idx ( net , iter , 0 ) ;
while ( + + iter - > ct < mrt - > maxvif ) {
if ( ! VIF_EXISTS ( mrt , iter - > ct ) )
continue ;
return & mrt - > vif_table [ iter - > ct ] ;
}
return NULL ;
}
EXPORT_SYMBOL ( mr_vif_seq_next ) ;
2018-03-01 00:29:36 +03:00
void * mr_mfc_seq_idx ( struct net * net ,
struct mr_mfc_iter * it , loff_t pos )
{
struct mr_table * mrt = it - > mrt ;
struct mr_mfc * mfc ;
rcu_read_lock ( ) ;
it - > cache = & mrt - > mfc_cache_list ;
list_for_each_entry_rcu ( mfc , & mrt - > mfc_cache_list , list )
if ( pos - - = = 0 )
return mfc ;
rcu_read_unlock ( ) ;
spin_lock_bh ( it - > lock ) ;
it - > cache = & mrt - > mfc_unres_queue ;
list_for_each_entry ( mfc , it - > cache , list )
if ( pos - - = = 0 )
return mfc ;
spin_unlock_bh ( it - > lock ) ;
it - > cache = NULL ;
return NULL ;
}
EXPORT_SYMBOL ( mr_mfc_seq_idx ) ;
void * mr_mfc_seq_next ( struct seq_file * seq , void * v ,
loff_t * pos )
{
struct mr_mfc_iter * it = seq - > private ;
struct net * net = seq_file_net ( seq ) ;
struct mr_table * mrt = it - > mrt ;
struct mr_mfc * c = v ;
+ + * pos ;
if ( v = = SEQ_START_TOKEN )
return mr_mfc_seq_idx ( net , seq - > private , 0 ) ;
if ( c - > list . next ! = it - > cache )
return list_entry ( c - > list . next , struct mr_mfc , list ) ;
if ( it - > cache = = & mrt - > mfc_unres_queue )
goto end_of_list ;
/* exhausted cache_array, show unresolved */
rcu_read_unlock ( ) ;
it - > cache = & mrt - > mfc_unres_queue ;
spin_lock_bh ( it - > lock ) ;
if ( ! list_empty ( it - > cache ) )
return list_first_entry ( it - > cache , struct mr_mfc , list ) ;
end_of_list :
spin_unlock_bh ( it - > lock ) ;
it - > cache = NULL ;
return NULL ;
}
EXPORT_SYMBOL ( mr_mfc_seq_next ) ;
# endif
2018-03-01 00:29:39 +03:00
int mr_fill_mroute ( struct mr_table * mrt , struct sk_buff * skb ,
struct mr_mfc * c , struct rtmsg * rtm )
{
struct rta_mfc_stats mfcs ;
struct nlattr * mp_attr ;
struct rtnexthop * nhp ;
unsigned long lastuse ;
int ct ;
/* If cache is unresolved, don't try to parse IIF and OIF */
if ( c - > mfc_parent > = MAXVIFS ) {
rtm - > rtm_flags | = RTNH_F_UNRESOLVED ;
return - ENOENT ;
}
if ( VIF_EXISTS ( mrt , c - > mfc_parent ) & &
nla_put_u32 ( skb , RTA_IIF ,
mrt - > vif_table [ c - > mfc_parent ] . dev - > ifindex ) < 0 )
return - EMSGSIZE ;
if ( c - > mfc_flags & MFC_OFFLOAD )
rtm - > rtm_flags | = RTNH_F_OFFLOAD ;
2019-04-26 12:13:06 +03:00
mp_attr = nla_nest_start_noflag ( skb , RTA_MULTIPATH ) ;
2018-03-01 00:29:39 +03:00
if ( ! mp_attr )
return - EMSGSIZE ;
for ( ct = c - > mfc_un . res . minvif ; ct < c - > mfc_un . res . maxvif ; ct + + ) {
if ( VIF_EXISTS ( mrt , ct ) & & c - > mfc_un . res . ttls [ ct ] < 255 ) {
struct vif_device * vif ;
nhp = nla_reserve_nohdr ( skb , sizeof ( * nhp ) ) ;
if ( ! nhp ) {
nla_nest_cancel ( skb , mp_attr ) ;
return - EMSGSIZE ;
}
nhp - > rtnh_flags = 0 ;
nhp - > rtnh_hops = c - > mfc_un . res . ttls [ ct ] ;
vif = & mrt - > vif_table [ ct ] ;
nhp - > rtnh_ifindex = vif - > dev - > ifindex ;
nhp - > rtnh_len = sizeof ( * nhp ) ;
}
}
nla_nest_end ( skb , mp_attr ) ;
lastuse = READ_ONCE ( c - > mfc_un . res . lastuse ) ;
lastuse = time_after_eq ( jiffies , lastuse ) ? jiffies - lastuse : 0 ;
mfcs . mfcs_packets = c - > mfc_un . res . pkt ;
mfcs . mfcs_bytes = c - > mfc_un . res . bytes ;
mfcs . mfcs_wrong_if = c - > mfc_un . res . wrong_if ;
if ( nla_put_64bit ( skb , RTA_MFC_STATS , sizeof ( mfcs ) , & mfcs , RTA_PAD ) | |
nla_put_u64_64bit ( skb , RTA_EXPIRES , jiffies_to_clock_t ( lastuse ) ,
RTA_PAD ) )
return - EMSGSIZE ;
rtm - > rtm_type = RTN_MULTICAST ;
return 1 ;
}
EXPORT_SYMBOL ( mr_fill_mroute ) ;
2018-10-16 04:56:47 +03:00
static bool mr_mfc_uses_dev ( const struct mr_table * mrt ,
const struct mr_mfc * c ,
const struct net_device * dev )
{
int ct ;
for ( ct = c - > mfc_un . res . minvif ; ct < c - > mfc_un . res . maxvif ; ct + + ) {
if ( VIF_EXISTS ( mrt , ct ) & & c - > mfc_un . res . ttls [ ct ] < 255 ) {
const struct vif_device * vif ;
vif = & mrt - > vif_table [ ct ] ;
if ( vif - > dev = = dev )
return true ;
}
}
return false ;
}
2018-10-16 04:56:46 +03:00
int mr_table_dump ( struct mr_table * mrt , struct sk_buff * skb ,
struct netlink_callback * cb ,
int ( * fill ) ( struct mr_table * mrt , struct sk_buff * skb ,
u32 portid , u32 seq , struct mr_mfc * c ,
int cmd , int flags ) ,
2018-10-16 04:56:47 +03:00
spinlock_t * lock , struct fib_dump_filter * filter )
2018-10-16 04:56:46 +03:00
{
unsigned int e = 0 , s_e = cb - > args [ 1 ] ;
unsigned int flags = NLM_F_MULTI ;
struct mr_mfc * mfc ;
int err ;
2018-10-16 04:56:47 +03:00
if ( filter - > filter_set )
flags | = NLM_F_DUMP_FILTERED ;
2018-10-16 04:56:46 +03:00
list_for_each_entry_rcu ( mfc , & mrt - > mfc_cache_list , list ) {
if ( e < s_e )
goto next_entry ;
2018-10-16 04:56:47 +03:00
if ( filter - > dev & &
! mr_mfc_uses_dev ( mrt , mfc , filter - > dev ) )
goto next_entry ;
2018-10-16 04:56:46 +03:00
err = fill ( mrt , skb , NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq , mfc , RTM_NEWROUTE , flags ) ;
if ( err < 0 )
goto out ;
next_entry :
e + + ;
}
spin_lock_bh ( lock ) ;
list_for_each_entry ( mfc , & mrt - > mfc_unres_queue , list ) {
if ( e < s_e )
goto next_entry2 ;
2018-10-16 04:56:47 +03:00
if ( filter - > dev & &
! mr_mfc_uses_dev ( mrt , mfc , filter - > dev ) )
goto next_entry2 ;
2018-10-16 04:56:46 +03:00
err = fill ( mrt , skb , NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq , mfc , RTM_NEWROUTE , flags ) ;
if ( err < 0 ) {
spin_unlock_bh ( lock ) ;
goto out ;
}
next_entry2 :
e + + ;
}
spin_unlock_bh ( lock ) ;
err = 0 ;
out :
cb - > args [ 1 ] = e ;
return err ;
}
2018-10-16 04:56:47 +03:00
EXPORT_SYMBOL ( mr_table_dump ) ;
2018-10-16 04:56:46 +03:00
2018-03-01 00:29:39 +03:00
int mr_rtm_dumproute ( struct sk_buff * skb , struct netlink_callback * cb ,
struct mr_table * ( * iter ) ( struct net * net ,
struct mr_table * mrt ) ,
int ( * fill ) ( struct mr_table * mrt ,
struct sk_buff * skb ,
u32 portid , u32 seq , struct mr_mfc * c ,
int cmd , int flags ) ,
2018-10-16 04:56:47 +03:00
spinlock_t * lock , struct fib_dump_filter * filter )
2018-03-01 00:29:39 +03:00
{
2018-10-16 04:56:46 +03:00
unsigned int t = 0 , s_t = cb - > args [ 0 ] ;
2018-03-01 00:29:39 +03:00
struct net * net = sock_net ( skb - > sk ) ;
struct mr_table * mrt ;
2018-10-16 04:56:46 +03:00
int err ;
2018-03-01 00:29:39 +03:00
2018-10-16 04:56:47 +03:00
/* multicast does not track protocol or have route type other
* than RTN_MULTICAST
*/
if ( filter - > filter_set ) {
if ( filter - > protocol | | filter - > flags | |
( filter - > rt_type & & filter - > rt_type ! = RTN_MULTICAST ) )
return skb - > len ;
}
2018-03-01 00:29:39 +03:00
rcu_read_lock ( ) ;
for ( mrt = iter ( net , NULL ) ; mrt ; mrt = iter ( net , mrt ) ) {
if ( t < s_t )
goto next_table ;
2018-10-16 04:56:46 +03:00
2018-10-16 04:56:47 +03:00
err = mr_table_dump ( mrt , skb , cb , fill , lock , filter ) ;
2018-10-16 04:56:46 +03:00
if ( err < 0 )
break ;
2019-05-03 01:14:15 +03:00
cb - > args [ 1 ] = 0 ;
2018-03-01 00:29:39 +03:00
next_table :
t + + ;
}
rcu_read_unlock ( ) ;
cb - > args [ 0 ] = t ;
return skb - > len ;
}
EXPORT_SYMBOL ( mr_rtm_dumproute ) ;
2018-03-26 15:01:33 +03:00
int mr_dump ( struct net * net , struct notifier_block * nb , unsigned short family ,
int ( * rules_dump ) ( struct net * net ,
2019-10-03 12:49:30 +03:00
struct notifier_block * nb ,
struct netlink_ext_ack * extack ) ,
2018-03-26 15:01:33 +03:00
struct mr_table * ( * mr_iter ) ( struct net * net ,
struct mr_table * mrt ) ,
2019-10-03 12:49:30 +03:00
rwlock_t * mrt_lock ,
struct netlink_ext_ack * extack )
2018-03-26 15:01:33 +03:00
{
struct mr_table * mrt ;
int err ;
2019-10-03 12:49:30 +03:00
err = rules_dump ( net , nb , extack ) ;
2018-03-26 15:01:33 +03:00
if ( err )
return err ;
for ( mrt = mr_iter ( net , NULL ) ; mrt ; mrt = mr_iter ( net , mrt ) ) {
struct vif_device * v = & mrt - > vif_table [ 0 ] ;
struct mr_mfc * mfc ;
int vifi ;
/* Notifiy on table VIF entries */
read_lock ( mrt_lock ) ;
for ( vifi = 0 ; vifi < mrt - > maxvif ; vifi + + , v + + ) {
if ( ! v - > dev )
continue ;
2019-10-03 12:49:28 +03:00
err = mr_call_vif_notifier ( nb , family ,
FIB_EVENT_VIF_ADD ,
2019-10-03 12:49:30 +03:00
v , vifi , mrt - > id , extack ) ;
2019-10-03 12:49:28 +03:00
if ( err )
break ;
2018-03-26 15:01:33 +03:00
}
read_unlock ( mrt_lock ) ;
2019-10-03 12:49:28 +03:00
if ( err )
return err ;
2018-03-26 15:01:33 +03:00
/* Notify on table MFC entries */
2019-10-03 12:49:28 +03:00
list_for_each_entry_rcu ( mfc , & mrt - > mfc_cache_list , list ) {
err = mr_call_mfc_notifier ( nb , family ,
FIB_EVENT_ENTRY_ADD ,
2019-10-03 12:49:30 +03:00
mfc , mrt - > id , extack ) ;
2019-10-03 12:49:28 +03:00
if ( err )
return err ;
}
2018-03-26 15:01:33 +03:00
}
return 0 ;
}
EXPORT_SYMBOL ( mr_dump ) ;