--- zzzz-none-000/linux-5.4.213/net/ipv4/ipmr.c 2022-09-15 10:04:56.000000000 +0000 +++ miami-7690-761/linux-5.4.213/net/ipv4/ipmr.c 2024-05-29 11:20:02.000000000 +0000 @@ -84,6 +84,9 @@ /* Special spinlock for queue of unresolved entries */ static DEFINE_SPINLOCK(mfc_unres_lock); +/* spinlock for offload */ +static DEFINE_SPINLOCK(lock); + /* We return to original Alan's scheme. Hash table of resolved * entries is changed only in process context and protected * with weak lock mrt_lock. Queue of unresolved entries is protected @@ -107,6 +110,9 @@ static void igmpmsg_netlink_event(struct mr_table *mrt, struct sk_buff *pkt); static void mroute_clean_tables(struct mr_table *mrt, int flags); static void ipmr_expire_process(struct timer_list *t); +static struct mfc_cache *ipmr_cache_find(struct mr_table *mrt, __be32 origin, + __be32 mcastgrp); +static ipmr_mfc_event_offload_callback_t __rcu ipmr_mfc_event_offload_callback; #ifdef CONFIG_IP_MROUTE_MULTIPLE_TABLES #define ipmr_for_each_table(mrt, net) \ @@ -173,6 +179,7 @@ case FR_ACT_UNREACHABLE: return -ENETUNREACH; case FR_ACT_PROHIBIT: + case FR_ACT_POLICY_FAILED: return -EACCES; case FR_ACT_BLACKHOLE: default: @@ -219,6 +226,78 @@ return 0; } +/* ipmr_sync_entry_update() + * Call the registered offload callback to report an update to a multicast + * route entry. The callback receives the list of destination interfaces and + * the interface count + */ +static void ipmr_sync_entry_update(struct mr_table *mrt, + struct mfc_cache *cache) +{ + int vifi, dest_if_count = 0; + u32 dest_dev[MAXVIFS]; + __be32 origin; + __be32 group; + ipmr_mfc_event_offload_callback_t offload_update_cb_f; + + memset(dest_dev, 0, sizeof(dest_dev)); + + origin = cache->mfc_origin; + group = cache->mfc_mcastgrp; + + read_lock(&mrt_lock); + for (vifi = 0; vifi < cache->_c.mfc_un.res.maxvif; vifi++) { + if (!((cache->_c.mfc_un.res.ttls[vifi] > 0) && + (cache->_c.mfc_un.res.ttls[vifi] < 255))) { + continue; + } + if (dest_if_count == MAXVIFS) { + read_unlock(&mrt_lock); + return; + } + + if (!VIF_EXISTS(mrt, vifi)) { + read_unlock(&mrt_lock); + return; + } + dest_dev[dest_if_count] = mrt->vif_table[vifi].dev->ifindex; + dest_if_count++; + } + read_unlock(&mrt_lock); + + rcu_read_lock(); + offload_update_cb_f = rcu_dereference(ipmr_mfc_event_offload_callback); + + if (!offload_update_cb_f) { + rcu_read_unlock(); + return; + } + + offload_update_cb_f(group, origin, dest_if_count, dest_dev, + IPMR_MFC_EVENT_UPDATE); + rcu_read_unlock(); +} + +/* ipmr_sync_entry_delete() + * Call the registered offload callback to inform of a multicast route entry + * delete event + */ +static void ipmr_sync_entry_delete(u32 origin, u32 group) +{ + ipmr_mfc_event_offload_callback_t offload_update_cb_f; + + rcu_read_lock(); + offload_update_cb_f = rcu_dereference(ipmr_mfc_event_offload_callback); + + if (!offload_update_cb_f) { + rcu_read_unlock(); + return; + } + + offload_update_cb_f(group, origin, 0, NULL, IPMR_MFC_EVENT_DELETE); + rcu_read_unlock(); +} + static const struct fib_rules_ops __net_initconst ipmr_rules_ops_template = { .family = RTNL_FAMILY_IPMR, .rule_size = sizeof(struct ipmr_rule), @@ -233,6 +312,154 @@ .owner = THIS_MODULE, }; +/* ipmr_register_mfc_event_offload_callback() + * Register the IPv4 Multicast update offload callback with IPMR + */ +bool ipmr_register_mfc_event_offload_callback( + ipmr_mfc_event_offload_callback_t mfc_offload_cb) +{ + ipmr_mfc_event_offload_callback_t offload_update_cb_f; + + rcu_read_lock(); + offload_update_cb_f = rcu_dereference(ipmr_mfc_event_offload_callback); + + if (offload_update_cb_f) { + rcu_read_unlock(); + return false; + } + rcu_read_unlock(); + + spin_lock(&lock); + rcu_assign_pointer(ipmr_mfc_event_offload_callback, mfc_offload_cb); + spin_unlock(&lock); + synchronize_rcu(); + return true; +} +EXPORT_SYMBOL(ipmr_register_mfc_event_offload_callback); + +/* ipmr_unregister_mfc_event_offload_callback() + * De-register the IPv4 Multicast update offload callback with IPMR + */ +void ipmr_unregister_mfc_event_offload_callback(void) +{ + spin_lock(&lock); + rcu_assign_pointer(ipmr_mfc_event_offload_callback, NULL); + spin_unlock(&lock); + synchronize_rcu(); +} +EXPORT_SYMBOL(ipmr_unregister_mfc_event_offload_callback); + +/* ipmr_find_mfc_entry() + * Returns destination interface list for a particular multicast flow, and + * the number of interfaces in the list + */ +int ipmr_find_mfc_entry(struct net *net, __be32 origin, __be32 group, + u32 max_dest_cnt, u32 dest_dev[]) +{ + int vifi, dest_if_count = 0; + struct mr_table *mrt; + struct mfc_cache *cache; + + mrt = ipmr_get_table(net, RT_TABLE_DEFAULT); + if (!mrt) + return -ENOENT; + + rcu_read_lock(); + cache = ipmr_cache_find(mrt, origin, group); + if (!cache) { + rcu_read_unlock(); + return -ENOENT; + } + + read_lock(&mrt_lock); + for (vifi = 0; vifi < cache->_c.mfc_un.res.maxvif; vifi++) { + if (!((cache->_c.mfc_un.res.ttls[vifi] > 0) && + (cache->_c.mfc_un.res.ttls[vifi] < 255))) { + continue; + } + + /* We have another valid destination interface entry. Check if + * the number of the destination interfaces for the route is + * exceeding the size of the array given to us + */ + if (dest_if_count == max_dest_cnt) { + read_unlock(&mrt_lock); + rcu_read_unlock(); + return -EINVAL; + } + + if (!VIF_EXISTS(mrt, vifi)) { + read_unlock(&mrt_lock); + rcu_read_unlock(); + return -EINVAL; + } + + dest_dev[dest_if_count] = mrt->vif_table[vifi].dev->ifindex; + dest_if_count++; + } + read_unlock(&mrt_lock); + rcu_read_unlock(); + + return dest_if_count; +} +EXPORT_SYMBOL(ipmr_find_mfc_entry); + +/* ipmr_mfc_stats_update() + * Update the MFC/VIF statistics for offloaded flows + */ +int ipmr_mfc_stats_update(struct net *net, __be32 origin, __be32 group, + u64 pkts_in, u64 bytes_in, + u64 pkts_out, u64 bytes_out) +{ + int vif, vifi; + struct mr_table *mrt; + struct mfc_cache *cache; + + mrt = ipmr_get_table(net, RT_TABLE_DEFAULT); + if (!mrt) + return -ENOENT; + + rcu_read_lock(); + cache = ipmr_cache_find(mrt, origin, group); + if (!cache) { + rcu_read_unlock(); + return -ENOENT; + } + + vif = cache->_c.mfc_parent; + + read_lock(&mrt_lock); + if (!VIF_EXISTS(mrt, vif)) { + read_unlock(&mrt_lock); + rcu_read_unlock(); + return -EINVAL; + } + + mrt->vif_table[vif].pkt_in += pkts_in; + mrt->vif_table[vif].bytes_in += bytes_in; + cache->_c.mfc_un.res.pkt += pkts_out; + cache->_c.mfc_un.res.bytes += bytes_out; + + for (vifi = cache->_c.mfc_un.res.minvif; + vifi < cache->_c.mfc_un.res.maxvif; vifi++) { + if ((cache->_c.mfc_un.res.ttls[vifi] > 0) && + (cache->_c.mfc_un.res.ttls[vifi] < 255)) { + if (!VIF_EXISTS(mrt, vifi)) { + read_unlock(&mrt_lock); + rcu_read_unlock(); + return -EINVAL; + } + mrt->vif_table[vifi].pkt_out += pkts_out; + mrt->vif_table[vifi].bytes_out += bytes_out; + } + } + read_unlock(&mrt_lock); + rcu_read_unlock(); + + return 0; +} +EXPORT_SYMBOL(ipmr_mfc_stats_update); + static int __net_init ipmr_rules_init(struct net *net) { struct fib_rules_ops *ops; @@ -1208,6 +1435,8 @@ call_ipmr_mfc_entry_notifiers(net, FIB_EVENT_ENTRY_DEL, c, mrt->id); mroute_netlink_event(mrt, c, RTM_DELROUTE); mr_cache_put(&c->_c); + /* Inform offload modules of the delete event */ + ipmr_sync_entry_delete(c->mfc_origin, c->mfc_mcastgrp); return 0; } @@ -1238,6 +1467,8 @@ call_ipmr_mfc_entry_notifiers(net, FIB_EVENT_ENTRY_REPLACE, c, mrt->id); mroute_netlink_event(mrt, c, RTM_NEWROUTE); + /* Inform offload modules of the update event */ + ipmr_sync_entry_update(mrt, c); return 0; } @@ -1298,6 +1529,7 @@ struct net *net = read_pnet(&mrt->net); struct mr_mfc *c, *tmp; struct mfc_cache *cache; + u32 origin, group; LIST_HEAD(list); int i; @@ -1322,10 +1554,14 @@ rhltable_remove(&mrt->mfc_hash, &c->mnode, ipmr_rht_params); list_del_rcu(&c->list); cache = (struct mfc_cache *)c; + origin = cache->mfc_origin; + group = cache->mfc_mcastgrp; call_ipmr_mfc_entry_notifiers(net, FIB_EVENT_ENTRY_DEL, cache, mrt->id); mroute_netlink_event(mrt, cache, RTM_DELROUTE); mr_cache_put(c); + /* Inform offload modules of the delete event */ + ipmr_sync_entry_delete(origin, group); } } @@ -2952,7 +3188,7 @@ vif->dev->name : "none"; seq_printf(seq, - "%2td %-10s %8ld %7ld %8ld %7ld %05X %08X %08X\n", + "%2td %-10s %8llu %7llu %8llu %7llu %05X %08X %08X\n", vif - mrt->vif_table, name, vif->bytes_in, vif->pkt_in, vif->bytes_out, vif->pkt_out,