diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c index 5425aec233..d1b4966ec9 100644 --- a/pimd/pim_iface.c +++ b/pimd/pim_iface.c @@ -127,7 +127,6 @@ struct pim_interface *pim_if_new(struct interface *ifp, bool igmp, bool pim, pim_ifp->pim = ifp->vrf->info; pim_ifp->mroute_vif_index = -1; -#if PIM_IPV == 4 pim_ifp->igmp_version = IGMP_DEFAULT_VERSION; pim_ifp->gm_default_robustness_variable = IGMP_DEFAULT_ROBUSTNESS_VARIABLE; @@ -153,10 +152,12 @@ struct pim_interface *pim_if_new(struct interface *ifp, bool igmp, bool pim, if (pim) PIM_IF_DO_PIM(pim_ifp->options); +#if PIM_IPV == 4 if (igmp) PIM_IF_DO_IGMP(pim_ifp->options); PIM_IF_DO_IGMP_LISTEN_ALLROUTERS(pim_ifp->options); +#endif pim_ifp->gm_join_list = NULL; pim_ifp->pim_neighbor_list = NULL; @@ -186,10 +187,11 @@ struct pim_interface *pim_if_new(struct interface *ifp, bool igmp, bool pim, ifp->info = pim_ifp; +#if PIM_IPV == 4 pim_sock_reset(ifp); +#endif pim_if_add_vif(ifp, ispimreg, is_vxlan_term); -#endif pim_ifp->pim->mcast_if_count++; return pim_ifp; @@ -208,9 +210,12 @@ void pim_if_delete(struct interface *ifp) if (pim_ifp->gm_join_list) { pim_if_igmp_join_del_all(ifp); } +#endif pim_ifchannel_delete_all(ifp); +#if PIM_IPV == 4 igmp_sock_delete_all(ifp); +#endif pim_neighbor_delete_all(ifp, "Interface removed from configuration"); @@ -224,7 +229,6 @@ void pim_if_delete(struct interface *ifp) XFREE(MTYPE_PIM_INTERFACE, pim_ifp->boundary_oil_plist); XFREE(MTYPE_PIM_INTERFACE, pim_ifp); -#endif ifp->info = NULL; } @@ -512,6 +516,26 @@ void pim_if_addr_add(struct connected *ifc) CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) ? "secondary" : "primary"); +#if PIM_IPV != 4 + if (IN6_IS_ADDR_LINKLOCAL(&ifc->address->u.prefix6) || + IN6_IS_ADDR_LOOPBACK(&ifc->address->u.prefix6)) { + if (IN6_IS_ADDR_UNSPECIFIED(&pim_ifp->ll_lowest)) + pim_ifp->ll_lowest = ifc->address->u.prefix6; + else if (IPV6_ADDR_CMP(&ifc->address->u.prefix6, + &pim_ifp->ll_lowest) < 0) + pim_ifp->ll_lowest = ifc->address->u.prefix6; + + if (IPV6_ADDR_CMP(&ifc->address->u.prefix6, + &pim_ifp->ll_highest) > 0) + pim_ifp->ll_highest = ifc->address->u.prefix6; + + if (PIM_DEBUG_ZEBRA) + zlog_debug( + "%s: new link-local %pI6, lowest now %pI6, highest %pI6", + ifc->ifp->name, &ifc->address->u.prefix6, + &pim_ifp->ll_lowest, &pim_ifp->ll_highest); + } +#endif detect_address_change(ifp, 0, __func__); @@ -711,6 +735,43 @@ void pim_if_addr_del(struct connected *ifc, int force_prim_as_any) ? "secondary" : "primary"); +#if PIM_IPV == 6 + struct pim_interface *pim_ifp = ifc->ifp->info; + + if (pim_ifp && + (!IPV6_ADDR_CMP(&ifc->address->u.prefix6, &pim_ifp->ll_lowest) || + !IPV6_ADDR_CMP(&ifc->address->u.prefix6, &pim_ifp->ll_highest))) { + struct listnode *cnode; + struct connected *cc; + + memset(&pim_ifp->ll_lowest, 0xff, sizeof(pim_ifp->ll_lowest)); + memset(&pim_ifp->ll_highest, 0, sizeof(pim_ifp->ll_highest)); + + for (ALL_LIST_ELEMENTS_RO(ifc->ifp->connected, cnode, cc)) { + if (!IN6_IS_ADDR_LINKLOCAL(&cc->address->u.prefix6) && + !IN6_IS_ADDR_LOOPBACK(&cc->address->u.prefix6)) + continue; + + if (IPV6_ADDR_CMP(&cc->address->u.prefix6, + &pim_ifp->ll_lowest) < 0) + pim_ifp->ll_lowest = cc->address->u.prefix6; + if (IPV6_ADDR_CMP(&cc->address->u.prefix6, + &pim_ifp->ll_highest) > 0) + pim_ifp->ll_highest = cc->address->u.prefix6; + } + + if (pim_ifp->ll_lowest.s6_addr[0] == 0xff) + memset(&pim_ifp->ll_lowest, 0, + sizeof(pim_ifp->ll_lowest)); + + if (PIM_DEBUG_ZEBRA) + zlog_debug( + "%s: removed link-local %pI6, lowest now %pI6, highest %pI6", + ifc->ifp->name, &ifc->address->u.prefix6, + &pim_ifp->ll_lowest, &pim_ifp->ll_highest); + } +#endif + detect_address_change(ifp, force_prim_as_any, __func__); pim_if_addr_del_igmp(ifc); @@ -825,17 +886,36 @@ pim_addr pim_find_primary_addr(struct interface *ifp) { struct connected *ifc; struct listnode *node; - int v4_addrs = 0; - int v6_addrs = 0; struct pim_interface *pim_ifp = ifp->info; - if (pim_ifp && !pim_addr_is_any(pim_ifp->update_source)) { + if (pim_ifp && !pim_addr_is_any(pim_ifp->update_source)) return pim_ifp->update_source; - } + +#if PIM_IPV == 6 + if (pim_ifp) + return pim_ifp->ll_highest; + + pim_addr best_addr = PIMADDR_ANY; for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { pim_addr addr; + if (ifc->address->family != AF_INET6) + continue; + + addr = pim_addr_from_prefix(ifc->address); + if (!IN6_IS_ADDR_LINKLOCAL(&addr)) + continue; + if (pim_addr_cmp(addr, best_addr) > 0) + best_addr = addr; + } + + return best_addr; +#else + int v4_addrs = 0; + int v6_addrs = 0; + + for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) { switch (ifc->address->family) { case AF_INET: v4_addrs++; @@ -853,16 +933,9 @@ pim_addr pim_find_primary_addr(struct interface *ifp) if (ifc->address->family != PIM_AF) continue; - addr = pim_addr_from_prefix(ifc->address); - -#if PIM_IPV == 6 - if (!IN6_IS_ADDR_LINKLOCAL(&addr)) - continue; -#endif - return addr; + return pim_addr_from_prefix(ifc->address); } -#if PIM_IPV == 4 /* * If we have no v4_addrs and v6 is configured * We probably are using unnumbered @@ -882,8 +955,8 @@ pim_addr pim_find_primary_addr(struct interface *ifp) if (lo_ifp && (lo_ifp != ifp)) return pim_find_primary_addr(lo_ifp); } -#endif return PIMADDR_ANY; +#endif } static int pim_iface_next_vif_index(struct interface *ifp) @@ -1549,7 +1622,6 @@ static int pim_ifp_create(struct interface *ifp) */ if (pim_ifp) pim_ifp->pim = pim; -#if PIM_IPV == 4 pim_if_addr_add_all(ifp); /* @@ -1561,7 +1633,6 @@ static int pim_ifp_create(struct interface *ifp) * this is a no-op if it's already been done. */ pim_if_create_pimreg(pim); -#endif } #if PIM_IPV == 4 @@ -1599,6 +1670,7 @@ static int pim_ifp_create(struct interface *ifp) static int pim_ifp_up(struct interface *ifp) { + uint32_t table_id; struct pim_interface *pim_ifp; struct pim_instance *pim; @@ -1621,9 +1693,6 @@ static int pim_ifp_up(struct interface *ifp) if (pim_ifp) pim_ifp->pim = pim; -#if PIM_IPV == 4 - uint32_t table_id; - /* pim_if_addr_add_all() suffices for bringing up both IGMP and PIM @@ -1652,7 +1721,6 @@ static int pim_ifp_up(struct interface *ifp) } } } -#endif return 0; } @@ -1666,7 +1734,6 @@ static int pim_ifp_down(struct interface *ifp) ifp->mtu, if_is_operative(ifp)); } -#if PIM_IPV == 4 if (!if_is_operative(ifp)) { pim_ifchannel_delete_all(ifp); /* @@ -1675,6 +1742,7 @@ static int pim_ifp_down(struct interface *ifp) */ pim_if_addr_del_all(ifp); +#if PIM_IPV == 4 /* pim_sock_delete() closes the socket, stops read and timer threads, @@ -1683,13 +1751,15 @@ static int pim_ifp_down(struct interface *ifp) if (ifp->info) { pim_sock_delete(ifp, "link down"); } +#endif } if (ifp->info) { pim_if_del_vif(ifp); +#if PIM_IPV == 4 pim_ifstat_reset(ifp); - } #endif + } return 0; } @@ -1704,12 +1774,12 @@ static int pim_ifp_destroy(struct interface *ifp) ifp->mtu, if_is_operative(ifp)); } -#if PIM_IPV == 4 - struct pim_instance *pim; - if (!if_is_operative(ifp)) pim_if_addr_del_all(ifp); +#if PIM_IPV == 4 + struct pim_instance *pim; + pim = ifp->vrf->info; if (pim && pim->vxlan.term_if == ifp) pim_vxlan_del_term_dev(pim); diff --git a/pimd/pim_iface.h b/pimd/pim_iface.h index 00ec8e7427..a1c946149d 100644 --- a/pimd/pim_iface.h +++ b/pimd/pim_iface.h @@ -96,6 +96,13 @@ struct pim_interface { uint32_t options; /* bit vector */ ifindex_t mroute_vif_index; struct pim_instance *pim; + +#if PIM_IPV == 6 + /* link-locals: MLD uses lowest addr, PIM uses highest... */ + pim_addr ll_lowest; + pim_addr ll_highest; +#endif + pim_addr primary_address; /* remember addr to detect change */ struct list *sec_addr_list; /* list of struct pim_secondary_addr */ pim_addr update_source; /* user can statically set the primary diff --git a/pimd/pim_igmp.c b/pimd/pim_igmp.c index 5cdefd2828..289e8e457d 100644 --- a/pimd/pim_igmp.c +++ b/pimd/pim_igmp.c @@ -37,11 +37,180 @@ #include "pim_str.h" #include "pim_util.h" #include "pim_time.h" -#include "pim_zebra.h" +#include "pim_ssm.h" +#include "pim_tib.h" static void group_timer_off(struct gm_group *group); static void pim_igmp_general_query(struct thread *t); +void igmp_anysource_forward_start(struct pim_instance *pim, + struct gm_group *group) +{ + struct gm_source *source; + struct in_addr src_addr = {.s_addr = 0}; + /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */ + assert(group->group_filtermode_isexcl); + assert(listcount(group->group_source_list) < 1); + + source = igmp_get_source_by_addr(group, src_addr, NULL); + if (!source) { + zlog_warn("%s: Failure to create * source", __func__); + return; + } + + igmp_source_forward_start(pim, source); +} + +void igmp_anysource_forward_stop(struct gm_group *group) +{ + struct gm_source *source; + struct in_addr star = {.s_addr = 0}; + + source = igmp_find_source_by_addr(group, star); + if (source) + igmp_source_forward_stop(source); +} + +static void igmp_source_forward_reevaluate_one(struct pim_instance *pim, + struct gm_source *source) +{ + pim_sgaddr sg; + struct gm_group *group = source->source_group; + struct pim_ifchannel *ch; + + if ((source->source_addr.s_addr != INADDR_ANY) || + !IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) + return; + + memset(&sg, 0, sizeof(sg)); + sg.src = source->source_addr; + sg.grp = group->group_addr; + + ch = pim_ifchannel_find(group->interface, &sg); + if (pim_is_grp_ssm(pim, group->group_addr)) { + /* If SSM group withdraw local membership */ + if (ch && + (ch->local_ifmembership == PIM_IFMEMBERSHIP_INCLUDE)) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "local membership del for %pSG as G is now SSM", + &sg); + pim_ifchannel_local_membership_del(group->interface, + &sg); + } + } else { + /* If ASM group add local membership */ + if (!ch || + (ch->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO)) { + if (PIM_DEBUG_PIM_EVENTS) + zlog_debug( + "local membership add for %pSG as G is now ASM", + &sg); + pim_ifchannel_local_membership_add( + group->interface, &sg, false /*is_vxlan*/); + } + } +} + +void igmp_source_forward_reevaluate_all(struct pim_instance *pim) +{ + struct interface *ifp; + + FOR_ALL_INTERFACES (pim->vrf, ifp) { + struct pim_interface *pim_ifp = ifp->info; + struct listnode *grpnode; + struct gm_group *grp; + struct pim_ifchannel *ch, *ch_temp; + + if (!pim_ifp) + continue; + + /* scan igmp groups */ + for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, grpnode, + grp)) { + struct listnode *srcnode; + struct gm_source *src; + + /* scan group sources */ + for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, + srcnode, src)) { + igmp_source_forward_reevaluate_one(pim, src); + } /* scan group sources */ + } /* scan igmp groups */ + + RB_FOREACH_SAFE (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb, + ch_temp) { + if (pim_is_grp_ssm(pim, ch->sg.grp)) { + if (pim_addr_is_any(ch->sg.src)) + pim_ifchannel_delete(ch); + } + } + } /* scan interfaces */ +} + +void igmp_source_forward_start(struct pim_instance *pim, + struct gm_source *source) +{ + struct gm_group *group; + pim_sgaddr sg; + + memset(&sg, 0, sizeof(sg)); + sg.src = source->source_addr; + sg.grp = source->source_group->group_addr; + + if (PIM_DEBUG_IGMP_TRACE) { + zlog_debug("%s: (S,G)=%pSG oif=%s fwd=%d", __func__, &sg, + source->source_group->interface->name, + IGMP_SOURCE_TEST_FORWARDING(source->source_flags)); + } + + /* Prevent IGMP interface from installing multicast route multiple + times */ + if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { + return; + } + + group = source->source_group; + + if (tib_sg_gm_join(pim, sg, group->interface, + &source->source_channel_oil)) + IGMP_SOURCE_DO_FORWARDING(source->source_flags); +} + +/* + igmp_source_forward_stop: stop fowarding, but keep the source + igmp_source_delete: stop fowarding, and delete the source + */ +void igmp_source_forward_stop(struct gm_source *source) +{ + struct pim_interface *pim_oif; + struct gm_group *group; + pim_sgaddr sg; + + memset(&sg, 0, sizeof(sg)); + sg.src = source->source_addr; + sg.grp = source->source_group->group_addr; + + if (PIM_DEBUG_IGMP_TRACE) { + zlog_debug("%s: (S,G)=%pSG oif=%s fwd=%d", __func__, &sg, + source->source_group->interface->name, + IGMP_SOURCE_TEST_FORWARDING(source->source_flags)); + } + + /* Prevent IGMP interface from removing multicast route multiple + times */ + if (!IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { + return; + } + + group = source->source_group; + pim_oif = group->interface->info; + + tib_sg_gm_prune(pim_oif->pim, sg, group->interface, + &source->source_channel_oil); + IGMP_SOURCE_DONT_FORWARDING(source->source_flags); +} + /* This socket is used for TXing IGMP packets only, IGMP RX happens * in pim_mroute_msg() */ diff --git a/pimd/pim_igmp.h b/pimd/pim_igmp.h index 5c35996634..4d7229dcce 100644 --- a/pimd/pim_igmp.h +++ b/pimd/pim_igmp.h @@ -128,6 +128,15 @@ void pim_igmp_other_querier_timer_off(struct gm_sock *igmp); int igmp_validate_checksum(char *igmp_msg, int igmp_msg_len); #else /* PIM_IPV != 4 */ +static inline void pim_igmp_if_init(struct pim_interface *pim_ifp, + struct interface *ifp) +{ +} + +static inline void pim_igmp_if_fini(struct pim_interface *pim_ifp) +{ +} + static inline void pim_igmp_general_query_on(struct gm_sock *igmp) { } @@ -204,6 +213,17 @@ struct gm_group { }; #if PIM_IPV == 4 +struct pim_instance; + +void igmp_anysource_forward_start(struct pim_instance *pim, + struct gm_group *group); +void igmp_anysource_forward_stop(struct gm_group *group); + +void igmp_source_forward_start(struct pim_instance *pim, + struct gm_source *source); +void igmp_source_forward_stop(struct gm_source *source); +void igmp_source_forward_reevaluate_all(struct pim_instance *pim); + struct gm_group *find_group_by_addr(struct gm_sock *igmp, struct in_addr group_addr); struct gm_group *igmp_add_group_by_addr(struct gm_sock *igmp, diff --git a/pimd/pim_oil.c b/pimd/pim_oil.c index a499c884b4..d5e459b44e 100644 --- a/pimd/pim_oil.c +++ b/pimd/pim_oil.c @@ -216,6 +216,10 @@ int pim_channel_del_oif(struct channel_oil *channel_oil, struct interface *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. @@ -418,6 +422,10 @@ int pim_channel_add_oif(struct channel_oil *channel_oil, struct interface *oif, 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) { diff --git a/pimd/pim_ssm.c b/pimd/pim_ssm.c index 688d38c84c..74310474d4 100644 --- a/pimd/pim_ssm.c +++ b/pimd/pim_ssm.c @@ -28,7 +28,7 @@ #include "pimd.h" #include "pim_ssm.h" -#include "pim_zebra.h" +#include "pim_igmp.h" static void pim_ssm_range_reevaluate(struct pim_instance *pim) { diff --git a/pimd/pim_tib.c b/pimd/pim_tib.c new file mode 100644 index 0000000000..838f11211e --- /dev/null +++ b/pimd/pim_tib.c @@ -0,0 +1,178 @@ +/* + * TIB (Tree Information Base) - just PIM <> IGMP/MLD glue for now + * Copyright (C) 2022 David Lamparter for NetDEF, Inc. + * + * 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 + +#include "pim_tib.h" + +#include "pimd.h" +#include "pim_iface.h" +#include "pim_upstream.h" +#include "pim_oil.h" +#include "pim_nht.h" + +static struct channel_oil * +tib_sg_oil_setup(struct pim_instance *pim, pim_sgaddr sg, struct interface *oif) +{ + struct pim_interface *pim_oif = oif->info; + int input_iface_vif_index = 0; + pim_addr vif_source; + struct prefix src, grp; + struct pim_nexthop nexthop; + struct pim_upstream *up = NULL; + + if (!pim_rp_set_upstream_addr(pim, &vif_source, sg.src, sg.grp)) { + /* no PIM RP - create a dummy channel oil */ + return pim_channel_oil_add(pim, &sg, __func__); + } + + pim_addr_to_prefix(&src, vif_source); // RP or Src addr + pim_addr_to_prefix(&grp, sg.grp); + + up = pim_upstream_find(pim, &sg); + if (up) { + memcpy(&nexthop, &up->rpf.source_nexthop, + sizeof(struct pim_nexthop)); + pim_ecmp_nexthop_lookup(pim, &nexthop, &src, &grp, 0); + if (nexthop.interface) + input_iface_vif_index = pim_if_find_vifindex_by_ifindex( + pim, nexthop.interface->ifindex); + } else + input_iface_vif_index = + pim_ecmp_fib_lookup_if_vif_index(pim, &src, &grp); + + if (PIM_DEBUG_ZEBRA) + zlog_debug("%s: NHT %pSG vif_source %pPAs vif_index:%d", + __func__, &sg, &vif_source, input_iface_vif_index); + + if (input_iface_vif_index < 1) { + if (PIM_DEBUG_IGMP_TRACE) + zlog_debug( + "%s %s: could not find input interface for %pSG", + __FILE__, __func__, &sg); + + return pim_channel_oil_add(pim, &sg, __func__); + } + + /* + * Protect IGMP against adding looped MFC entries created by both + * source and receiver attached to the same interface. See TODO T22. + * Block only when the intf is non DR DR must create upstream. + */ + if ((input_iface_vif_index == pim_oif->mroute_vif_index) && + !(PIM_I_am_DR(pim_oif))) { + /* ignore request for looped MFC entry */ + if (PIM_DEBUG_IGMP_TRACE) + zlog_debug( + "%s: ignoring request for looped MFC entry (S,G)=%pSG: oif=%s vif_index=%d", + __func__, &sg, oif->name, + input_iface_vif_index); + + return NULL; + } + + return pim_channel_oil_add(pim, &sg, __func__); +} + +bool tib_sg_gm_join(struct pim_instance *pim, pim_sgaddr sg, + struct interface *oif, struct channel_oil **oilp) +{ + struct pim_interface *pim_oif = oif->info; + + if (!pim_oif) { + if (PIM_DEBUG_IGMP_TRACE) + zlog_debug("%s: multicast not enabled on oif=%s?", + __func__, oif->name); + return false; + } + + if (!*oilp) + *oilp = tib_sg_oil_setup(pim, sg, oif); + if (!*oilp) + return false; + + if (PIM_I_am_DR(pim_oif) || PIM_I_am_DualActive(pim_oif)) { + int result; + + result = pim_channel_add_oif(*oilp, oif, + PIM_OIF_FLAG_PROTO_IGMP, __func__); + if (result) { + if (PIM_DEBUG_MROUTE) + zlog_warn("%s: add_oif() failed with return=%d", + __func__, result); + return false; + } + } else { + if (PIM_DEBUG_IGMP_TRACE) + zlog_debug( + "%s: %pSG was received on %s interface but we are not DR for that interface", + __func__, &sg, oif->name); + + return false; + } + /* + Feed IGMPv3-gathered local membership information into PIM + per-interface (S,G) state. + */ + if (!pim_ifchannel_local_membership_add(oif, &sg, false /*is_vxlan*/)) { + if (PIM_DEBUG_MROUTE) + zlog_warn( + "%s: Failure to add local membership for %pSG", + __func__, &sg); + + pim_channel_del_oif(*oilp, oif, PIM_OIF_FLAG_PROTO_IGMP, + __func__); + return false; + } + + return true; +} + +void tib_sg_gm_prune(struct pim_instance *pim, pim_sgaddr sg, + struct interface *oif, struct channel_oil **oilp) +{ + int result; + + /* + It appears that in certain circumstances that + igmp_source_forward_stop is called when IGMP forwarding + was not enabled in oif_flags for this outgoing interface. + Possibly because of multiple calls. When that happens, we + enter the below if statement and this function returns early + which in turn triggers the calling function to assert. + Making the call to pim_channel_del_oif and ignoring the return code + fixes the issue without ill effect, similar to + pim_forward_stop below. + */ + result = pim_channel_del_oif(*oilp, oif, PIM_OIF_FLAG_PROTO_IGMP, + __func__); + if (result) { + if (PIM_DEBUG_IGMP_TRACE) + zlog_debug( + "%s: pim_channel_del_oif() failed with return=%d", + __func__, result); + return; + } + + /* + Feed IGMPv3-gathered local membership information into PIM + per-interface (S,G) state. + */ + pim_ifchannel_local_membership_del(oif, &sg); +} diff --git a/pimd/pim_tib.h b/pimd/pim_tib.h new file mode 100644 index 0000000000..b320f4cde0 --- /dev/null +++ b/pimd/pim_tib.h @@ -0,0 +1,33 @@ +/* + * TIB (Tree Information Base) - just PIM <> IGMP/MLD glue for now + * Copyright (C) 2022 David Lamparter for NetDEF, Inc. + * + * 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 + */ + +#ifndef _FRR_PIM_GLUE_H +#define _FRR_PIM_GLUE_H + +#include "pim_addr.h" + +struct pim_instance; +struct channel_oil; + +extern bool tib_sg_gm_join(struct pim_instance *pim, pim_sgaddr sg, + struct interface *oif, struct channel_oil **oilp); +extern void tib_sg_gm_prune(struct pim_instance *pim, pim_sgaddr sg, + struct interface *oif, struct channel_oil **oilp); + +#endif /* _FRR_PIM_GLUE_H */ diff --git a/pimd/pim_zebra.c b/pimd/pim_zebra.c index 526fdcbc27..7f463715a5 100644 --- a/pimd/pim_zebra.c +++ b/pimd/pim_zebra.c @@ -55,7 +55,6 @@ struct zclient *zclient; /* Router-id update message from zebra. */ -__attribute__((unused)) static int pim_router_id_update_zebra(ZAPI_CALLBACK_ARGS) { struct prefix router_id; @@ -65,7 +64,6 @@ static int pim_router_id_update_zebra(ZAPI_CALLBACK_ARGS) return 0; } -__attribute__((unused)) static int pim_zebra_interface_vrf_update(ZAPI_CALLBACK_ARGS) { struct interface *ifp; @@ -158,6 +156,10 @@ static int pim_zebra_if_address_add(ZAPI_CALLBACK_ARGS) SET_FLAG(c->flags, ZEBRA_IFA_SECONDARY); } } +#else /* PIM_IPV != 4 */ + if (p->family != PIM_AF) + return 0; +#endif pim_if_addr_add(c); if (pim_ifp) { @@ -178,10 +180,6 @@ static int pim_zebra_if_address_add(ZAPI_CALLBACK_ARGS) pim_if_addr_add_all(ifp); } } -#else /* PIM_IPV != 4 */ - /* unused - for now */ - (void)pim_ifp; -#endif return 0; } @@ -220,8 +218,7 @@ static int pim_zebra_if_address_del(ZAPI_CALLBACK_ARGS) #endif } -#if PIM_IPV == 4 - if (p->family == AF_INET) { + if (p->family == PIM_AF) { struct pim_instance *pim; pim = vrf->info; @@ -229,7 +226,6 @@ static int pim_zebra_if_address_del(ZAPI_CALLBACK_ARGS) pim_rp_setup(pim); pim_i_am_rp_re_evaluate(pim); } -#endif connected_free(&c); return 0; @@ -456,10 +452,10 @@ static zclient_handler *const pim_handlers[] = { [ZEBRA_INTERFACE_ADDRESS_DELETE] = pim_zebra_if_address_del, [ZEBRA_NEXTHOP_UPDATE] = pim_parse_nexthop_update, -#if PIM_IPV == 4 [ZEBRA_ROUTER_ID_UPDATE] = pim_router_id_update_zebra, [ZEBRA_INTERFACE_VRF_UPDATE] = pim_zebra_interface_vrf_update, +#if PIM_IPV == 4 [ZEBRA_VXLAN_SG_ADD] = pim_zebra_vxlan_sg_proc, [ZEBRA_VXLAN_SG_DEL] = pim_zebra_vxlan_sg_proc, @@ -486,335 +482,6 @@ void pim_zebra_init(void) zclient_lookup_new(); } -#if PIM_IPV == 4 -void igmp_anysource_forward_start(struct pim_instance *pim, - struct gm_group *group) -{ - struct gm_source *source; - struct in_addr src_addr = {.s_addr = 0}; - /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */ - assert(group->group_filtermode_isexcl); - assert(listcount(group->group_source_list) < 1); - - source = igmp_get_source_by_addr(group, src_addr, NULL); - if (!source) { - zlog_warn("%s: Failure to create * source", __func__); - return; - } - - igmp_source_forward_start(pim, source); -} - -void igmp_anysource_forward_stop(struct gm_group *group) -{ - struct gm_source *source; - struct in_addr star = {.s_addr = 0}; - - source = igmp_find_source_by_addr(group, star); - if (source) - igmp_source_forward_stop(source); -} - -static void igmp_source_forward_reevaluate_one(struct pim_instance *pim, - struct gm_source *source) -{ - pim_sgaddr sg; - struct gm_group *group = source->source_group; - struct pim_ifchannel *ch; - - if ((source->source_addr.s_addr != INADDR_ANY) - || !IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) - return; - - memset(&sg, 0, sizeof(sg)); - sg.src = source->source_addr; - sg.grp = group->group_addr; - - ch = pim_ifchannel_find(group->interface, &sg); - if (pim_is_grp_ssm(pim, group->group_addr)) { - /* If SSM group withdraw local membership */ - if (ch - && (ch->local_ifmembership == PIM_IFMEMBERSHIP_INCLUDE)) { - if (PIM_DEBUG_PIM_EVENTS) - zlog_debug("local membership del for %pSG as G is now SSM", - &sg); - pim_ifchannel_local_membership_del(group->interface, - &sg); - } - } else { - /* If ASM group add local membership */ - if (!ch - || (ch->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO)) { - if (PIM_DEBUG_PIM_EVENTS) - zlog_debug("local membership add for %pSG as G is now ASM", - &sg); - pim_ifchannel_local_membership_add( - group->interface, &sg, false /*is_vxlan*/); - } - } -} - -void igmp_source_forward_reevaluate_all(struct pim_instance *pim) -{ - struct interface *ifp; - - FOR_ALL_INTERFACES (pim->vrf, ifp) { - struct pim_interface *pim_ifp = ifp->info; - struct listnode *grpnode; - struct gm_group *grp; - struct pim_ifchannel *ch, *ch_temp; - - if (!pim_ifp) - continue; - - /* scan igmp groups */ - for (ALL_LIST_ELEMENTS_RO(pim_ifp->gm_group_list, grpnode, - grp)) { - struct listnode *srcnode; - struct gm_source *src; - - /* scan group sources */ - for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, - srcnode, src)) { - igmp_source_forward_reevaluate_one(pim, src); - } /* scan group sources */ - } /* scan igmp groups */ - - RB_FOREACH_SAFE (ch, pim_ifchannel_rb, &pim_ifp->ifchannel_rb, - ch_temp) { - if (pim_is_grp_ssm(pim, ch->sg.grp)) { - if (pim_addr_is_any(ch->sg.src)) - pim_ifchannel_delete(ch); - } - } - } /* scan interfaces */ -} - -void igmp_source_forward_start(struct pim_instance *pim, - struct gm_source *source) -{ - struct pim_interface *pim_oif; - struct gm_group *group; - pim_sgaddr sg; - int result; - int input_iface_vif_index = 0; - - memset(&sg, 0, sizeof(sg)); - sg.src = source->source_addr; - sg.grp = source->source_group->group_addr; - - if (PIM_DEBUG_IGMP_TRACE) { - zlog_debug("%s: (S,G)=%pSG oif=%s fwd=%d", __func__, &sg, - source->source_group->interface->name, - IGMP_SOURCE_TEST_FORWARDING(source->source_flags)); - } - - /* Prevent IGMP interface from installing multicast route multiple - times */ - if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { - return; - } - - group = source->source_group; - pim_oif = group->interface->info; - if (!pim_oif) { - if (PIM_DEBUG_IGMP_TRACE) { - zlog_debug("%s: multicast not enabled on oif=%s ?", - __func__, - source->source_group->interface->name); - } - return; - } - - if (!source->source_channel_oil) { - pim_addr vif_source; - struct prefix src, grp; - struct pim_nexthop nexthop; - struct pim_upstream *up = NULL; - - if (!pim_rp_set_upstream_addr(pim, &vif_source, - source->source_addr, sg.grp)) { - /*Create a dummy channel oil */ - source->source_channel_oil = - pim_channel_oil_add(pim, &sg, __func__); - } - - else { - pim_addr_to_prefix(&src, vif_source); // RP or Src addr - pim_addr_to_prefix(&grp, sg.grp); - - up = pim_upstream_find(pim, &sg); - if (up) { - memcpy(&nexthop, &up->rpf.source_nexthop, - sizeof(struct pim_nexthop)); - pim_ecmp_nexthop_lookup(pim, &nexthop, &src, - &grp, 0); - if (nexthop.interface) - input_iface_vif_index = - pim_if_find_vifindex_by_ifindex( - pim, - nexthop.interface->ifindex); - } else - input_iface_vif_index = - pim_ecmp_fib_lookup_if_vif_index( - pim, &src, &grp); - - if (PIM_DEBUG_ZEBRA) - zlog_debug( - "%s: NHT %pSG vif_source %pPAs vif_index:%d ", - __func__, &sg, &vif_source, - input_iface_vif_index); - - if (input_iface_vif_index < 1) { - if (PIM_DEBUG_IGMP_TRACE) { - char source_str[INET_ADDRSTRLEN]; - pim_inet4_dump("", - source->source_addr, - source_str, sizeof(source_str)); - zlog_debug( - "%s %s: could not find input interface for source %s", - __FILE__, __func__, source_str); - } - source->source_channel_oil = - pim_channel_oil_add(pim, &sg, __func__); - } - - else { - /* - * Protect IGMP against adding looped MFC - * entries created by both source and receiver - * attached to the same interface. See TODO - * T22. Block only when the intf is non DR - * DR must create upstream. - */ - if ((input_iface_vif_index == - pim_oif->mroute_vif_index) && - !(PIM_I_am_DR(pim_oif))) { - /* ignore request for looped MFC entry - */ - if (PIM_DEBUG_IGMP_TRACE) { - zlog_debug("%s: ignoring request for looped MFC entry (S,G)=%pSG: oif=%s vif_index=%d", - __func__, - &sg, - source->source_group - ->interface->name, - input_iface_vif_index); - } - return; - } - - source->source_channel_oil = - pim_channel_oil_add(pim, &sg, __func__); - if (!source->source_channel_oil) { - if (PIM_DEBUG_IGMP_TRACE) { - zlog_debug("%s %s: could not create OIL for channel (S,G)=%pSG", - __FILE__, __func__, - &sg); - } - return; - } - } - } - } - - if (PIM_I_am_DR(pim_oif) || PIM_I_am_DualActive(pim_oif)) { - result = pim_channel_add_oif(source->source_channel_oil, - group->interface, - PIM_OIF_FLAG_PROTO_IGMP, __func__); - if (result) { - if (PIM_DEBUG_MROUTE) { - zlog_warn("%s: add_oif() failed with return=%d", - __func__, result); - } - return; - } - } else { - if (PIM_DEBUG_IGMP_TRACE) - zlog_debug("%s: %pSG was received on %s interface but we are not DR for that interface", - __func__, &sg, - group->interface->name); - - return; - } - /* - Feed IGMPv3-gathered local membership information into PIM - per-interface (S,G) state. - */ - if (!pim_ifchannel_local_membership_add(group->interface, &sg, - false /*is_vxlan*/)) { - if (PIM_DEBUG_MROUTE) - zlog_warn("%s: Failure to add local membership for %pSG", - __func__, &sg); - - pim_channel_del_oif(source->source_channel_oil, - group->interface, PIM_OIF_FLAG_PROTO_IGMP, - __func__); - return; - } - - IGMP_SOURCE_DO_FORWARDING(source->source_flags); -} - -/* - igmp_source_forward_stop: stop fowarding, but keep the source - igmp_source_delete: stop fowarding, and delete the source - */ -void igmp_source_forward_stop(struct gm_source *source) -{ - struct gm_group *group; - pim_sgaddr sg; - int result; - - memset(&sg, 0, sizeof(sg)); - sg.src = source->source_addr; - sg.grp = source->source_group->group_addr; - - if (PIM_DEBUG_IGMP_TRACE) { - zlog_debug("%s: (S,G)=%pSG oif=%s fwd=%d", __func__, &sg, - source->source_group->interface->name, - IGMP_SOURCE_TEST_FORWARDING(source->source_flags)); - } - - /* Prevent IGMP interface from removing multicast route multiple - times */ - if (!IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) { - return; - } - - group = source->source_group; - - /* - It appears that in certain circumstances that - igmp_source_forward_stop is called when IGMP forwarding - was not enabled in oif_flags for this outgoing interface. - Possibly because of multiple calls. When that happens, we - enter the below if statement and this function returns early - which in turn triggers the calling function to assert. - Making the call to pim_channel_del_oif and ignoring the return code - fixes the issue without ill effect, similar to - pim_forward_stop below. - */ - result = pim_channel_del_oif(source->source_channel_oil, - group->interface, PIM_OIF_FLAG_PROTO_IGMP, - __func__); - if (result) { - if (PIM_DEBUG_IGMP_TRACE) - zlog_debug( - "%s: pim_channel_del_oif() failed with return=%d", - __func__, result); - return; - } - - /* - Feed IGMPv3-gathered local membership information into PIM - per-interface (S,G) state. - */ - pim_ifchannel_local_membership_del(group->interface, &sg); - - IGMP_SOURCE_DONT_FORWARDING(source->source_flags); -} -#endif /* PIM_IPV == 4 */ - void pim_forward_start(struct pim_ifchannel *ch) { struct pim_upstream *up = ch->upstream; diff --git a/pimd/pim_zebra.h b/pimd/pim_zebra.h index 8656d7563d..5879cdefb0 100644 --- a/pimd/pim_zebra.h +++ b/pimd/pim_zebra.h @@ -23,7 +23,6 @@ #include #include "zclient.h" -#include "pim_igmp.h" #include "pim_ifchannel.h" void pim_zebra_init(void); @@ -32,15 +31,6 @@ void pim_zebra_zclient_update(struct vty *vty); void pim_scan_individual_oil(struct channel_oil *c_oil, int in_vif_index); void pim_scan_oil(struct pim_instance *pim_matcher); -void igmp_anysource_forward_start(struct pim_instance *pim, - struct gm_group *group); -void igmp_anysource_forward_stop(struct gm_group *group); - -void igmp_source_forward_start(struct pim_instance *pim, - struct gm_source *source); -void igmp_source_forward_stop(struct gm_source *source); -void igmp_source_forward_reevaluate_all(struct pim_instance *pim); - void pim_forward_start(struct pim_ifchannel *ch); void pim_forward_stop(struct pim_ifchannel *ch); diff --git a/pimd/subdir.am b/pimd/subdir.am index d617be3cb7..cda5acb004 100644 --- a/pimd/subdir.am +++ b/pimd/subdir.am @@ -47,6 +47,7 @@ pim_common = \ pimd/pim_ssmpingd.c \ pimd/pim_static.c \ pimd/pim_str.c \ + pimd/pim_tib.c \ pimd/pim_time.c \ pimd/pim_tlv.c \ pimd/pim_upstream.c \ @@ -141,6 +142,7 @@ noinst_HEADERS += \ pimd/pim_ssmpingd.h \ pimd/pim_static.h \ pimd/pim_str.h \ + pimd/pim_tib.h \ pimd/pim_time.h \ pimd/pim_tlv.h \ pimd/pim_upstream.h \