--- zzzz-none-000/linux-5.4.213/net/ipv6/ip6mr.c 2022-09-15 10:04:56.000000000 +0000 +++ miami-7690-761/linux-5.4.213/net/ipv6/ip6mr.c 2024-05-29 11:20:02.000000000 +0000 @@ -69,6 +69,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 @@ -94,10 +97,15 @@ struct netlink_callback *cb); static void mroute_clean_tables(struct mr_table *mrt, int flags); static void ipmr_expire_process(struct timer_list *t); +static struct mfc6_cache *ip6mr_cache_find(struct mr_table *mrt, + const struct in6_addr *origin, + const struct in6_addr *mcastgrp); +static ip6mr_mfc_event_offload_callback_t __rcu + ip6mr_mfc_event_offload_callback; #ifdef CONFIG_IPV6_MROUTE_MULTIPLE_TABLES #define ip6mr_for_each_table(mrt, net) \ - list_for_each_entry_rcu(mrt, &net->ipv6.mr6_tables, list) + list_for_each_entry_rcu(mrt, &net->ipv6.mr_tables, list) static struct mr_table *ip6mr_mr_table_iter(struct net *net, struct mr_table *mrt) @@ -161,6 +169,8 @@ return -ENETUNREACH; case FR_ACT_PROHIBIT: return -EACCES; + case FR_ACT_POLICY_FAILED: + return -EACCES; case FR_ACT_BLACKHOLE: default: return -EINVAL; @@ -369,6 +379,82 @@ .mf6c_mcastgrp = IN6ADDR_ANY_INIT, }; +/* ip6mr_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 ip6mr_sync_entry_update(struct mr_table *mrt, + struct mfc6_cache *cache) +{ + int vifi, dest_if_count = 0; + u32 dest_dev[MAXMIFS]; + struct in6_addr mc_origin, mc_group; + ip6mr_mfc_event_offload_callback_t offload_update_cb_f; + + memset(dest_dev, 0, sizeof(dest_dev)); + + 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 == MAXMIFS) { + 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++; + } + + memcpy(&mc_origin, &cache->mf6c_origin, sizeof(struct in6_addr)); + memcpy(&mc_group, &cache->mf6c_mcastgrp, sizeof(struct in6_addr)); + read_unlock(&mrt_lock); + + rcu_read_lock(); + offload_update_cb_f = rcu_dereference(ip6mr_mfc_event_offload_callback); + + if (!offload_update_cb_f) { + rcu_read_unlock(); + return; + } + + offload_update_cb_f(&mc_group, &mc_origin, dest_if_count, dest_dev, + IP6MR_MFC_EVENT_UPDATE); + rcu_read_unlock(); +} + +/* ip6mr_sync_entry_delete() + * Call the registered offload callback to inform of a multicast route entry + * delete event + */ +static void ip6mr_sync_entry_delete(struct in6_addr *mc_origin, + struct in6_addr *mc_group) +{ + ip6mr_mfc_event_offload_callback_t offload_update_cb_f; + + rcu_read_lock(); + offload_update_cb_f = rcu_dereference(ip6mr_mfc_event_offload_callback); + + if (!offload_update_cb_f) { + rcu_read_unlock(); + return; + } + + offload_update_cb_f(mc_group, mc_origin, 0, NULL, + IP6MR_MFC_EVENT_DELETE); + rcu_read_unlock(); +} + static struct mr_table_ops ip6mr_mr_table_ops = { .rht_params = &ip6mr_rht_params, .cmparg_any = &ip6mr_mr_table_ops_cmparg_any, @@ -436,7 +522,7 @@ const char *name = vif->dev ? vif->dev->name : "none"; seq_printf(seq, - "%2td %-10s %8ld %7ld %8ld %7ld %05X\n", + "%2td %-10s %8llu %7llu %8llu %7llu %05X\n", vif - mrt->vif_table, name, vif->bytes_in, vif->pkt_in, vif->bytes_out, vif->pkt_out, @@ -689,6 +775,149 @@ &mfc->_c, tb_id, &net->ipv6.ipmr_seq); } +/* ip6mr_register_mfc_event_offload_callback() + * Register the IPv6 multicast update callback for offload modules + */ +bool ip6mr_register_mfc_event_offload_callback( + ip6mr_mfc_event_offload_callback_t mfc_offload_cb) +{ + ip6mr_mfc_event_offload_callback_t offload_update_cb_f; + + rcu_read_lock(); + offload_update_cb_f = rcu_dereference(ip6mr_mfc_event_offload_callback); + + if (offload_update_cb_f) { + rcu_read_unlock(); + return false; + } + rcu_read_unlock(); + + spin_lock(&lock); + rcu_assign_pointer(ip6mr_mfc_event_offload_callback, mfc_offload_cb); + spin_unlock(&lock); + synchronize_rcu(); + return true; +} +EXPORT_SYMBOL(ip6mr_register_mfc_event_offload_callback); + +/* ip6mr_unregister_mfc_event_offload_callback() + * De-register the IPv6 multicast update callback for offload modules + */ +void ip6mr_unregister_mfc_event_offload_callback(void) +{ + spin_lock(&lock); + rcu_assign_pointer(ip6mr_mfc_event_offload_callback, NULL); + spin_unlock(&lock); + synchronize_rcu(); +} +EXPORT_SYMBOL(ip6mr_unregister_mfc_event_offload_callback); + +/* ip6mr_find_mfc_entry() + * Return the destination interface list for a particular multicast flow, and + * the number of interfaces in the list + */ +int ip6mr_find_mfc_entry(struct net *net, struct in6_addr *origin, + struct in6_addr *group, u32 max_dest_cnt, + u32 dest_dev[]) +{ + int vifi, dest_if_count = 0; + struct mr_table *mrt; + struct mfc6_cache *cache; + + mrt = ip6mr_get_table(net, RT6_TABLE_DFLT); + if (!mrt) + return -ENOENT; + + read_lock(&mrt_lock); + cache = ip6mr_cache_find(mrt, origin, group); + if (!cache) { + read_unlock(&mrt_lock); + return -ENOENT; + } + + 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); + return -EINVAL; + } + + if (!VIF_EXISTS(mrt, vifi)) { + read_unlock(&mrt_lock); + return -EINVAL; + } + + dest_dev[dest_if_count] = mrt->vif_table[vifi].dev->ifindex; + dest_if_count++; + } + read_unlock(&mrt_lock); + + return dest_if_count; +} +EXPORT_SYMBOL(ip6mr_find_mfc_entry); + +/* ip6mr_mfc_stats_update() + * Update the MFC/VIF statistics for offloaded flows + */ +int ip6mr_mfc_stats_update(struct net *net, struct in6_addr *origin, + struct in6_addr *group, u64 pkts_in, + u64 bytes_in, uint64_t pkts_out, + u64 bytes_out) +{ + int vif, vifi; + struct mr_table *mrt; + struct mfc6_cache *cache; + + mrt = ip6mr_get_table(net, RT6_TABLE_DFLT); + + if (!mrt) + return -ENOENT; + + read_lock(&mrt_lock); + cache = ip6mr_cache_find(mrt, origin, group); + if (!cache) { + read_unlock(&mrt_lock); + return -ENOENT; + } + + vif = cache->_c.mfc_parent; + + if (!VIF_EXISTS(mrt, vif)) { + read_unlock(&mrt_lock); + 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); + return -EINVAL; + } + mrt->vif_table[vifi].pkt_out += pkts_out; + mrt->vif_table[vifi].bytes_out += bytes_out; + } + } + + read_unlock(&mrt_lock); + return 0; +} +EXPORT_SYMBOL(ip6mr_mfc_stats_update); + /* Delete a VIF entry */ static int mif6_delete(struct mr_table *mrt, int vifi, int notify, struct list_head *head) @@ -1210,6 +1439,7 @@ int parent) { struct mfc6_cache *c; + struct in6_addr mc_origin, mc_group; /* The entries are added/deleted only under RTNL */ rcu_read_lock(); @@ -1218,6 +1448,9 @@ rcu_read_unlock(); if (!c) return -ENOENT; + + memcpy(&mc_origin, &c->mf6c_origin, sizeof(struct in6_addr)); + memcpy(&mc_group, &c->mf6c_mcastgrp, sizeof(struct in6_addr)); rhltable_remove(&mrt->mfc_hash, &c->_c.mnode, ip6mr_rht_params); list_del_rcu(&c->_c.list); @@ -1225,6 +1458,9 @@ FIB_EVENT_ENTRY_DEL, c, mrt->id); mr6_netlink_event(mrt, c, RTM_DELROUTE); mr_cache_put(&c->_c); + /* Inform offload modules of the delete event */ + ip6mr_sync_entry_delete(&mc_origin, &mc_group); + return 0; } @@ -1433,6 +1669,9 @@ call_ip6mr_mfc_entry_notifiers(net, FIB_EVENT_ENTRY_REPLACE, c, mrt->id); mr6_netlink_event(mrt, c, RTM_NEWROUTE); + + /* Inform offload modules of the update event */ + ip6mr_sync_entry_update(mrt, c); return 0; } @@ -1495,7 +1734,9 @@ static void mroute_clean_tables(struct mr_table *mrt, int flags) { + struct mfc6_cache *cache; struct mr_mfc *c, *tmp; + struct in6_addr mc_origin, mc_group; LIST_HEAD(list); int i; @@ -1517,13 +1758,18 @@ if (((c->mfc_flags & MFC_STATIC) && !(flags & MRT6_FLUSH_MFC_STATIC)) || (!(c->mfc_flags & MFC_STATIC) && !(flags & MRT6_FLUSH_MFC))) continue; + cache = (struct mfc6_cache *)c; + memcpy(&mc_origin, &cache->mf6c_origin, sizeof(struct in6_addr)); + memcpy(&mc_group, &cache->mf6c_mcastgrp, sizeof(struct in6_addr)); rhltable_remove(&mrt->mfc_hash, &c->mnode, ip6mr_rht_params); list_del_rcu(&c->list); call_ip6mr_mfc_entry_notifiers(read_pnet(&mrt->net), FIB_EVENT_ENTRY_DEL, - (struct mfc6_cache *)c, mrt->id); - mr6_netlink_event(mrt, (struct mfc6_cache *)c, RTM_DELROUTE); + cache, mrt->id); + mr6_netlink_event(mrt, cache, RTM_DELROUTE); mr_cache_put(c); + /* Inform offload modules of the delete event */ + ip6mr_sync_entry_delete(&mc_origin, &mc_group); } }