summaryrefslogtreecommitdiffstats
path: root/pimd/pim_oil.c
diff options
context:
space:
mode:
Diffstat (limited to 'pimd/pim_oil.c')
-rw-r--r--pimd/pim_oil.c589
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);
+}