diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 09:53:30 +0000 |
commit | 2c7cac91ed6e7db0f6937923d2b57f97dbdbc337 (patch) | |
tree | c05dc0f8e6aa3accc84e3e5cffc933ed94941383 /pimd/pim_oil.c | |
parent | Initial commit. (diff) | |
download | frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.tar.xz frr-2c7cac91ed6e7db0f6937923d2b57f97dbdbc337.zip |
Adding upstream version 8.4.4.upstream/8.4.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | pimd/pim_oil.c | 589 |
1 files changed, 589 insertions, 0 deletions
diff --git a/pimd/pim_oil.c b/pimd/pim_oil.c new file mode 100644 index 0000000..5c8d816 --- /dev/null +++ b/pimd/pim_oil.c @@ -0,0 +1,589 @@ +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; see the file COPYING; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <zebra.h> + +#include "log.h" +#include "memory.h" +#include "linklist.h" +#include "if.h" +#include "hash.h" +#include "jhash.h" + +#include "pimd.h" +#include "pim_oil.h" +#include "pim_str.h" +#include "pim_iface.h" +#include "pim_time.h" +#include "pim_vxlan.h" + +static void pim_channel_update_mute(struct channel_oil *c_oil); + +char *pim_channel_oil_dump(struct channel_oil *c_oil, char *buf, size_t size) +{ + char *out; + struct interface *ifp; + pim_sgaddr sg; + int i; + + sg.src = *oil_origin(c_oil); + sg.grp = *oil_mcastgrp(c_oil); + ifp = pim_if_find_by_vif_index(c_oil->pim, *oil_parent(c_oil)); + snprintfrr(buf, size, "%pSG IIF: %s, OIFS: ", &sg, + ifp ? ifp->name : "(?)"); + + out = buf + strlen(buf); + for (i = 0; i < MAXVIFS; i++) { + if (oil_if_has(c_oil, i) != 0) { + ifp = pim_if_find_by_vif_index(c_oil->pim, i); + snprintf(out, buf + size - out, "%s ", + ifp ? ifp->name : "(?)"); + out += strlen(out); + } + } + + return buf; +} + +int pim_channel_oil_compare(const struct channel_oil *cc1, + const struct channel_oil *cc2) +{ + struct channel_oil *c1 = (struct channel_oil *)cc1; + struct channel_oil *c2 = (struct channel_oil *)cc2; + int rv; + + rv = pim_addr_cmp(*oil_mcastgrp(c1), *oil_mcastgrp(c2)); + if (rv) + return rv; + rv = pim_addr_cmp(*oil_origin(c1), *oil_origin(c2)); + if (rv) + return rv; + return 0; +} + +void pim_oil_init(struct pim_instance *pim) +{ + rb_pim_oil_init(&pim->channel_oil_head); +} + +void pim_oil_terminate(struct pim_instance *pim) +{ + struct channel_oil *c_oil; + + while ((c_oil = rb_pim_oil_pop(&pim->channel_oil_head))) + pim_channel_oil_free(c_oil); + + rb_pim_oil_fini(&pim->channel_oil_head); +} + +void pim_channel_oil_free(struct channel_oil *c_oil) +{ + XFREE(MTYPE_PIM_CHANNEL_OIL, c_oil); +} + +struct channel_oil *pim_find_channel_oil(struct pim_instance *pim, + pim_sgaddr *sg) +{ + struct channel_oil *c_oil = NULL; + struct channel_oil lookup; + + *oil_mcastgrp(&lookup) = sg->grp; + *oil_origin(&lookup) = sg->src; + + c_oil = rb_pim_oil_find(&pim->channel_oil_head, &lookup); + + return c_oil; +} + +struct channel_oil *pim_channel_oil_add(struct pim_instance *pim, + pim_sgaddr *sg, const char *name) +{ + struct channel_oil *c_oil; + + c_oil = pim_find_channel_oil(pim, sg); + if (c_oil) { + ++c_oil->oil_ref_count; + + if (!c_oil->up) { + /* channel might be present prior to upstream */ + c_oil->up = pim_upstream_find( + pim, sg); + /* if the upstream entry is being anchored to an + * already existing channel OIL we need to re-evaluate + * the "Mute" state on AA OIFs + */ + pim_channel_update_mute(c_oil); + } + + /* check if the IIF has changed + * XXX - is this really needed + */ + pim_upstream_mroute_iif_update(c_oil, __func__); + + if (PIM_DEBUG_MROUTE) + zlog_debug( + "%s(%s): Existing oil for %pSG Ref Count: %d (Post Increment)", + __func__, name, sg, c_oil->oil_ref_count); + return c_oil; + } + + c_oil = XCALLOC(MTYPE_PIM_CHANNEL_OIL, sizeof(*c_oil)); + + *oil_mcastgrp(c_oil) = sg->grp; + *oil_origin(c_oil) = sg->src; + + *oil_parent(c_oil) = MAXVIFS; + c_oil->oil_ref_count = 1; + c_oil->installed = 0; + c_oil->up = pim_upstream_find(pim, sg); + c_oil->pim = pim; + + rb_pim_oil_add(&pim->channel_oil_head, c_oil); + + if (PIM_DEBUG_MROUTE) + zlog_debug("%s(%s): c_oil %pSG add", __func__, name, sg); + + return c_oil; +} + + +/* + * Clean up mroute and channel oil created for dropping pkts from directly + * connected source when the interface was non DR. + */ +void pim_clear_nocache_state(struct pim_interface *pim_ifp) +{ + struct channel_oil *c_oil; + + frr_each_safe (rb_pim_oil, &pim_ifp->pim->channel_oil_head, c_oil) { + + if ((!c_oil->up) || + !(PIM_UPSTREAM_FLAG_TEST_SRC_NOCACHE(c_oil->up->flags))) + continue; + + if (*oil_parent(c_oil) != pim_ifp->mroute_vif_index) + continue; + + THREAD_OFF(c_oil->up->t_ka_timer); + PIM_UPSTREAM_FLAG_UNSET_SRC_NOCACHE(c_oil->up->flags); + PIM_UPSTREAM_FLAG_UNSET_SRC_STREAM(c_oil->up->flags); + pim_upstream_del(pim_ifp->pim, c_oil->up, __func__); + } +} + +struct channel_oil *pim_channel_oil_del(struct channel_oil *c_oil, + const char *name) +{ + if (PIM_DEBUG_MROUTE) { + pim_sgaddr sg = {.src = *oil_mcastgrp(c_oil), + .grp = *oil_origin(c_oil)}; + + zlog_debug( + "%s(%s): Del oil for %pSG, Ref Count: %d (Predecrement)", + __func__, name, &sg, c_oil->oil_ref_count); + } + --c_oil->oil_ref_count; + + if (c_oil->oil_ref_count < 1) { + /* + * notice that listnode_delete() can't be moved + * into pim_channel_oil_free() because the later is + * called by list_delete_all_node() + */ + c_oil->up = NULL; + rb_pim_oil_del(&c_oil->pim->channel_oil_head, c_oil); + + pim_channel_oil_free(c_oil); + return NULL; + } + + return c_oil; +} + +void pim_channel_oil_upstream_deref(struct channel_oil *c_oil) +{ + /* The upstream entry associated with a channel_oil is abt to be + * deleted. If the channel_oil is kept around because of other + * references we need to remove upstream based states out of it. + */ + c_oil = pim_channel_oil_del(c_oil, __func__); + if (c_oil) { + /* note: here we assume that c_oil->up has already been + * cleared + */ + pim_channel_update_mute(c_oil); + } +} + +int pim_channel_del_oif(struct channel_oil *channel_oil, struct interface *oif, + uint32_t proto_mask, const char *caller) +{ + struct pim_interface *pim_ifp; + + assert(channel_oil); + assert(oif); + + pim_ifp = oif->info; + + assertf(pim_ifp->mroute_vif_index >= 0, + "trying to del OIF %s with VIF (%d)", oif->name, + pim_ifp->mroute_vif_index); + + /* + * Don't do anything if we've been asked to remove a source + * that is not actually on it. + */ + if (!(channel_oil->oif_flags[pim_ifp->mroute_vif_index] & proto_mask)) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: no existing protocol mask %u(%u) for requested OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, proto_mask, + channel_oil + ->oif_flags[pim_ifp->mroute_vif_index], + oif->name, pim_ifp->mroute_vif_index, + oil_if_has(channel_oil, pim_ifp->mroute_vif_index), + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + return 0; + } + + channel_oil->oif_flags[pim_ifp->mroute_vif_index] &= ~proto_mask; + + if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & + PIM_OIF_FLAG_PROTO_ANY) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: other protocol masks remain for requested OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, oif->name, + pim_ifp->mroute_vif_index, + oil_if_has(channel_oil, pim_ifp->mroute_vif_index), + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + return 0; + } + + oil_if_set(channel_oil, pim_ifp->mroute_vif_index, 0); + /* clear mute; will be re-evaluated when the OIF becomes valid again */ + channel_oil->oif_flags[pim_ifp->mroute_vif_index] &= ~PIM_OIF_FLAG_MUTE; + + if (pim_upstream_mroute_add(channel_oil, __func__)) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: could not remove output interface %s (vif_index=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, oif->name, + pim_ifp->mroute_vif_index, + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + return -1; + } + + --channel_oil->oil_size; + + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s(%s): (S,G)=(%pPAs,%pPAs): proto_mask=%u IIF:%d OIF=%s vif_index=%d", + __func__, caller, oil_origin(channel_oil), + oil_mcastgrp(channel_oil), + proto_mask, + *oil_parent(channel_oil), oif->name, + pim_ifp->mroute_vif_index); + } + + return 0; +} + +void pim_channel_del_inherited_oif(struct channel_oil *c_oil, + struct interface *oif, const char *caller) +{ + struct pim_upstream *up = c_oil->up; + + pim_channel_del_oif(c_oil, oif, PIM_OIF_FLAG_PROTO_STAR, + caller); + + /* if an inherited OIF is being removed join-desired can change + * if the inherited OIL is now empty and KAT is running + */ + if (up && !pim_addr_is_any(up->sg.src) && + pim_upstream_empty_inherited_olist(up)) + pim_upstream_update_join_desired(up->pim, up); +} + +static bool pim_channel_eval_oif_mute(struct channel_oil *c_oil, + struct pim_interface *pim_ifp) +{ + struct pim_interface *pim_reg_ifp; + struct pim_interface *vxlan_ifp; + bool do_mute = false; + struct pim_instance *pim = c_oil->pim; + + if (!c_oil->up) + return do_mute; + + pim_reg_ifp = pim->regiface->info; + if (pim_ifp == pim_reg_ifp) { + /* suppress pimreg in the OIL if the mroute is not supposed to + * trigger register encapsulated data + */ + if (PIM_UPSTREAM_FLAG_TEST_NO_PIMREG_DATA(c_oil->up->flags)) + do_mute = true; + + return do_mute; + } + + vxlan_ifp = pim_vxlan_get_term_ifp(pim); + if (pim_ifp == vxlan_ifp) { + /* 1. vxlan termination device must never be added to the + * origination mroute (and that can actually happen because + * of XG inheritance from the termination mroute) otherwise + * traffic will end up looping. + * PS: This check has also been extended to non-orig mroutes + * that have a local SIP as such mroutes can move back and + * forth between orig<=>non-orig type. + * 2. vxlan termination device should be removed from the non-DF + * to prevent duplicates to the overlay rxer + */ + if (PIM_UPSTREAM_FLAG_TEST_SRC_VXLAN_ORIG(c_oil->up->flags) || + PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(c_oil->up->flags) || + pim_vxlan_is_local_sip(c_oil->up)) + do_mute = true; + + return do_mute; + } + + if (PIM_I_am_DualActive(pim_ifp)) { + struct pim_upstream *starup = c_oil->up->parent; + if (PIM_UPSTREAM_FLAG_TEST_MLAG_INTERFACE(c_oil->up->flags) + && (PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(c_oil->up->flags))) + do_mute = true; + + /* In case entry is (S,G), Negotiation happens at (*.G) */ + if (starup + + && PIM_UPSTREAM_FLAG_TEST_MLAG_INTERFACE(starup->flags) + && (PIM_UPSTREAM_FLAG_TEST_MLAG_NON_DF(starup->flags))) + do_mute = true; + return do_mute; + } + return do_mute; +} + +void pim_channel_update_oif_mute(struct channel_oil *c_oil, + struct pim_interface *pim_ifp) +{ + bool old_mute; + bool new_mute; + + /* If pim_ifp is not a part of the OIL there is nothing to do */ + if (!oil_if_has(c_oil, pim_ifp->mroute_vif_index)) + return; + + old_mute = !!(c_oil->oif_flags[pim_ifp->mroute_vif_index] & + PIM_OIF_FLAG_MUTE); + new_mute = pim_channel_eval_oif_mute(c_oil, pim_ifp); + if (old_mute == new_mute) + return; + + if (new_mute) + c_oil->oif_flags[pim_ifp->mroute_vif_index] |= + PIM_OIF_FLAG_MUTE; + else + c_oil->oif_flags[pim_ifp->mroute_vif_index] &= + ~PIM_OIF_FLAG_MUTE; + + pim_upstream_mroute_add(c_oil, __func__); +} + +/* pim_upstream has been set or cleared on the c_oil. re-eval mute state + * on all existing OIFs + */ +static void pim_channel_update_mute(struct channel_oil *c_oil) +{ + struct pim_interface *pim_reg_ifp; + struct pim_interface *vxlan_ifp; + + if (c_oil->pim->regiface) { + pim_reg_ifp = c_oil->pim->regiface->info; + if (pim_reg_ifp) + pim_channel_update_oif_mute(c_oil, pim_reg_ifp); + } + vxlan_ifp = pim_vxlan_get_term_ifp(c_oil->pim); + if (vxlan_ifp) + pim_channel_update_oif_mute(c_oil, vxlan_ifp); +} + +int pim_channel_add_oif(struct channel_oil *channel_oil, struct interface *oif, + uint32_t proto_mask, const char *caller) +{ + struct pim_interface *pim_ifp; + int old_ttl; + + /* + * If we've gotten here we've gone bad, but let's + * not take down pim + */ + if (!channel_oil) { + zlog_warn("Attempt to Add OIF for non-existent channel oil"); + return -1; + } + + pim_ifp = oif->info; + + assertf(pim_ifp->mroute_vif_index >= 0, + "trying to add OIF %s with VIF (%d)", oif->name, + pim_ifp->mroute_vif_index); + + /* Prevent single protocol from subscribing same interface to + channel (S,G) multiple times */ + if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & proto_mask) { + channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= proto_mask; + + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: existing protocol mask %u requested OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, proto_mask, oif->name, + pim_ifp->mroute_vif_index, + oil_if_has(channel_oil, pim_ifp->mroute_vif_index), + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + return -3; + } + + /* Allow other protocol to request subscription of same interface to + * channel (S,G), we need to note this information + */ + if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] + & PIM_OIF_FLAG_PROTO_ANY) { + + /* Updating time here is not required as this time has to + * indicate when the interface is added + */ + + channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= proto_mask; + /* Check the OIF really exists before returning, and only log + warning otherwise */ + if (oil_if_has(channel_oil, pim_ifp->mroute_vif_index) < 1) { + zlog_warn( + "%s %s: new protocol mask %u requested nonexistent OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, proto_mask, oif->name, + pim_ifp->mroute_vif_index, + oil_if_has(channel_oil, pim_ifp->mroute_vif_index), + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s(%s): (S,G)=(%pPAs,%pPAs): proto_mask=%u OIF=%s vif_index=%d added to 0x%x", + __func__, caller, oil_origin(channel_oil), + oil_mcastgrp(channel_oil), + proto_mask, oif->name, + pim_ifp->mroute_vif_index, + channel_oil + ->oif_flags[pim_ifp->mroute_vif_index]); + } + return 0; + } + + old_ttl = oil_if_has(channel_oil, pim_ifp->mroute_vif_index); + + if (old_ttl > 0) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: interface %s (vif_index=%d) is existing output for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, oif->name, + pim_ifp->mroute_vif_index, + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + return -4; + } + + oil_if_set(channel_oil, pim_ifp->mroute_vif_index, PIM_MROUTE_MIN_TTL); + + /* Some OIFs are held in a muted state i.e. the PIM state machine + * decided to include the OIF but additional status check such as + * MLAG DF role prevent it from being activated for traffic + * forwarding. + */ + if (pim_channel_eval_oif_mute(channel_oil, pim_ifp)) + channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= + PIM_OIF_FLAG_MUTE; + else + channel_oil->oif_flags[pim_ifp->mroute_vif_index] &= + ~PIM_OIF_FLAG_MUTE; + + /* channel_oil->oil.mfcc_parent != MAXVIFS indicate this entry is not + * valid to get installed in kernel. + */ + if (*oil_parent(channel_oil) != MAXVIFS) { + if (pim_upstream_mroute_add(channel_oil, __func__)) { + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s %s: could not add output interface %s (vif_index=%d) for channel (S,G)=(%pPAs,%pPAs)", + __FILE__, __func__, oif->name, + pim_ifp->mroute_vif_index, + oil_origin(channel_oil), + oil_mcastgrp(channel_oil)); + } + + oil_if_set(channel_oil, pim_ifp->mroute_vif_index, + old_ttl); + return -5; + } + } + + channel_oil->oif_creation[pim_ifp->mroute_vif_index] = + pim_time_monotonic_sec(); + ++channel_oil->oil_size; + channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= proto_mask; + + if (PIM_DEBUG_MROUTE) { + zlog_debug( + "%s(%s): (S,G)=(%pPAs,%pPAs): proto_mask=%u OIF=%s vif_index=%d: DONE", + __func__, caller, oil_origin(channel_oil), + oil_mcastgrp(channel_oil), + proto_mask, + oif->name, pim_ifp->mroute_vif_index); + } + + return 0; +} + +int pim_channel_oil_empty(struct channel_oil *c_oil) +{ + static struct channel_oil null_oil; + + if (!c_oil) + return 1; + + /* exclude pimreg from the OIL when checking if the inherited_oil is + * non-NULL. + * pimreg device (in all vrfs) uses a vifi of + * 0 (PIM_OIF_PIM_REGISTER_VIF) so we simply mfcc_ttls[0] */ + if (oil_if_has(c_oil, 0)) + oil_if_set(&null_oil, 0, 1); + else + oil_if_set(&null_oil, 0, 0); + + return !oil_if_cmp(&c_oil->oil, &null_oil.oil); +} |