diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-09 13:16:35 +0000 |
commit | e2bbf175a2184bd76f6c54ccf8456babeb1a46fc (patch) | |
tree | f0b76550d6e6f500ada964a3a4ee933a45e5a6f1 /pimd/pim_join.c | |
parent | Initial commit. (diff) | |
download | frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.tar.xz frr-e2bbf175a2184bd76f6c54ccf8456babeb1a46fc.zip |
Adding upstream version 9.1.upstream/9.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'pimd/pim_join.c')
-rw-r--r-- | pimd/pim_join.c | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/pimd/pim_join.c b/pimd/pim_join.c new file mode 100644 index 0000000..671f7a3 --- /dev/null +++ b/pimd/pim_join.c @@ -0,0 +1,604 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + + +/* + * PIM for Quagga + * Copyright (C) 2008 Everton da Silva Marques + */ + +#include <zebra.h> + +#include "log.h" +#include "prefix.h" +#include "if.h" +#include "vty.h" +#include "plist.h" + +#include "pimd.h" +#include "pim_instance.h" +#include "pim_str.h" +#include "pim_tlv.h" +#include "pim_msg.h" +#include "pim_pim.h" +#include "pim_join.h" +#include "pim_oil.h" +#include "pim_iface.h" +#include "pim_hello.h" +#include "pim_ifchannel.h" +#include "pim_rpf.h" +#include "pim_rp.h" +#include "pim_jp_agg.h" +#include "pim_util.h" +#include "pim_ssm.h" + +static void on_trace(const char *label, struct interface *ifp, pim_addr src) +{ + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: from %pPA on %s", label, &src, ifp->name); +} + +static void recv_join(struct interface *ifp, struct pim_neighbor *neigh, + uint16_t holdtime, pim_addr upstream, pim_sgaddr *sg, + uint8_t source_flags) +{ + struct pim_interface *pim_ifp = NULL; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: join (S,G)=%pSG rpt=%d wc=%d upstream=%pPAs holdtime=%d from %pPA on %s", + __func__, sg, !!(source_flags & PIM_RPT_BIT_MASK), + !!(source_flags & PIM_WILDCARD_BIT_MASK), &upstream, + holdtime, &neigh->source_addr, ifp->name); + + pim_ifp = ifp->info; + assert(pim_ifp); + + ++pim_ifp->pim_ifstat_join_recv; + + /* + * If the RPT and WC are set it's a (*,G) + * and the source is the RP + */ + if (CHECK_FLAG(source_flags, PIM_WILDCARD_BIT_MASK)) { + /* As per RFC 7761 Section 4.9.1: + * The RPT (or Rendezvous Point Tree) bit is a 1-bit value for + * use with PIM Join/Prune messages (see Section 4.9.5.1). If + * the WC bit is 1, the RPT bit MUST be 1. + */ + if (!CHECK_FLAG(source_flags, PIM_RPT_BIT_MASK)) { + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "Discarding (*,G)=%pSG join since WC bit is set but RPT bit is unset", + sg); + + return; + } + + struct pim_rpf *rp = RP(pim_ifp->pim, sg->grp); + pim_addr rpf_addr; + + if (!rp) { + zlog_warn("%s: Lookup of RP failed for %pSG", __func__, + sg); + return; + } + /* + * If the RP sent in the message is not + * our RP for the group, drop the message + */ + rpf_addr = rp->rpf_addr; + if (pim_addr_cmp(sg->src, rpf_addr)) { + zlog_warn( + "%s: Specified RP(%pPAs) in join is different than our configured RP(%pPAs)", + __func__, &sg->src, &rpf_addr); + return; + } + + if (pim_is_grp_ssm(pim_ifp->pim, sg->grp)) { + zlog_warn( + "%s: Specified Group(%pPA) in join is now in SSM, not allowed to create PIM state", + __func__, &sg->grp); + return; + } + + sg->src = PIMADDR_ANY; + } + + /* Restart join expiry timer */ + pim_ifchannel_join_add(ifp, neigh->source_addr, upstream, sg, + source_flags, holdtime); +} + +static void recv_prune(struct interface *ifp, struct pim_neighbor *neigh, + uint16_t holdtime, pim_addr upstream, pim_sgaddr *sg, + uint8_t source_flags) +{ + struct pim_interface *pim_ifp = NULL; + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: prune (S,G)=%pSG rpt=%d wc=%d upstream=%pPAs holdtime=%d from %pPA on %s", + __func__, sg, source_flags & PIM_RPT_BIT_MASK, + source_flags & PIM_WILDCARD_BIT_MASK, &upstream, + holdtime, &neigh->source_addr, ifp->name); + + pim_ifp = ifp->info; + assert(pim_ifp); + + ++pim_ifp->pim_ifstat_prune_recv; + + if (CHECK_FLAG(source_flags, PIM_WILDCARD_BIT_MASK)) { + /* As per RFC 7761 Section 4.9.1: + * The RPT (or Rendezvous Point Tree) bit is a 1-bit value for + * use with PIM Join/Prune messages (see Section 4.9.5.1). If + * the WC bit is 1, the RPT bit MUST be 1. + */ + if (!CHECK_FLAG(source_flags, PIM_RPT_BIT_MASK)) { + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "Discarding (*,G)=%pSG prune since WC bit is set but RPT bit is unset", + sg); + + return; + } + + /* + * RFC 4601 Section 4.5.2: + * Received Prune(*,G) messages are processed even if the + * RP in the message does not match RP(G). + */ + if (PIM_DEBUG_PIM_TRACE) + zlog_debug("%s: Prune received with RP(%pPAs) for %pSG", + __func__, &sg->src, sg); + + sg->src = PIMADDR_ANY; + } + + pim_ifchannel_prune(ifp, upstream, sg, source_flags, holdtime); +} + +int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh, + pim_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size) +{ + pim_addr msg_upstream_addr; + bool wrong_af = false; + struct pim_interface *pim_ifp; + uint8_t msg_num_groups; + uint16_t msg_holdtime; + int addr_offset; + uint8_t *buf; + uint8_t *pastend; + int remain; + int group; + struct pim_ifchannel *child = NULL; + struct listnode *ch_node, *nch_node; + + buf = tlv_buf; + pastend = tlv_buf + tlv_buf_size; + pim_ifp = ifp->info; + + if (pim_ifp->pim_passive_enable) { + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug( + "skip receiving PIM message on passive interface %s", + ifp->name); + return 0; + } + + /* + Parse ucast addr + */ + addr_offset = pim_parse_addr_ucast(&msg_upstream_addr, buf, + pastend - buf, &wrong_af); + if (addr_offset < 1) { + zlog_warn("%s: pim_parse_addr_ucast() failure: from %pPA on %s", + __func__, &src_addr, ifp->name); + return -1; + } + buf += addr_offset; + + /* + Check upstream address family + */ + if (wrong_af) { + zlog_warn( + "%s: ignoring join/prune directed to unexpected addr family from %pPA on %s", + __func__, &src_addr, ifp->name); + return -2; + } + + remain = pastend - buf; + if (remain < 4) { + zlog_warn( + "%s: short join/prune message buffer for group list: size=%d minimum=%d from %pPA on %s", + __func__, remain, 4, &src_addr, ifp->name); + return -4; + } + + ++buf; /* skip reserved byte */ + msg_num_groups = *(const uint8_t *)buf; + ++buf; + msg_holdtime = ntohs(*(const uint16_t *)buf); + ++buf; + ++buf; + + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "%s: join/prune upstream=%pPAs groups=%d holdtime=%d from %pPA on %s", + __func__, &msg_upstream_addr, msg_num_groups, + msg_holdtime, &src_addr, ifp->name); + + /* Scan groups */ + for (group = 0; group < msg_num_groups; ++group) { + pim_sgaddr sg; + uint8_t msg_source_flags; + uint16_t msg_num_joined_sources; + uint16_t msg_num_pruned_sources; + int source; + struct pim_ifchannel *starg_ch = NULL, *sg_ch = NULL; + bool filtered = false; + + memset(&sg, 0, sizeof(sg)); + addr_offset = pim_parse_addr_group(&sg, buf, pastend - buf); + if (addr_offset < 1) { + return -5; + } + buf += addr_offset; + + remain = pastend - buf; + if (remain < 4) { + zlog_warn( + "%s: short join/prune buffer for source list: size=%d minimum=%d from %pPA on %s", + __func__, remain, 4, &src_addr, ifp->name); + return -6; + } + + msg_num_joined_sources = ntohs(*(const uint16_t *)buf); + buf += 2; + msg_num_pruned_sources = ntohs(*(const uint16_t *)buf); + buf += 2; + + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "%s: join/prune upstream=%pPAs group=%pPA/32 join_src=%d prune_src=%d from %pPA on %s", + __func__, &msg_upstream_addr, &sg.grp, + msg_num_joined_sources, msg_num_pruned_sources, + &src_addr, ifp->name); + + /* boundary check */ + filtered = pim_is_group_filtered(pim_ifp, &sg.grp); + + /* Scan joined sources */ + for (source = 0; source < msg_num_joined_sources; ++source) { + addr_offset = pim_parse_addr_source( + &sg, &msg_source_flags, buf, pastend - buf); + if (addr_offset < 1) { + return -7; + } + + buf += addr_offset; + + /* if we are filtering this group, skip the join */ + if (filtered) + continue; + + recv_join(ifp, neigh, msg_holdtime, msg_upstream_addr, + &sg, msg_source_flags); + + if (pim_addr_is_any(sg.src)) { + starg_ch = pim_ifchannel_find(ifp, &sg); + if (starg_ch) + pim_ifchannel_set_star_g_join_state( + starg_ch, 0, 1); + } + } + + /* Scan pruned sources */ + for (source = 0; source < msg_num_pruned_sources; ++source) { + addr_offset = pim_parse_addr_source( + &sg, &msg_source_flags, buf, pastend - buf); + if (addr_offset < 1) { + return -8; + } + + buf += addr_offset; + + /* if we are filtering this group, skip the prune */ + if (filtered) + continue; + + recv_prune(ifp, neigh, msg_holdtime, msg_upstream_addr, + &sg, msg_source_flags); + /* + * So if we are receiving a S,G,RPT prune + * before we have any data for that S,G + * We need to retrieve the sg_ch after + * we parse the prune. + */ + sg_ch = pim_ifchannel_find(ifp, &sg); + + if (!sg_ch) + continue; + + /* (*,G) prune received */ + for (ALL_LIST_ELEMENTS(sg_ch->sources, ch_node, + nch_node, child)) { + if (PIM_IF_FLAG_TEST_S_G_RPT(child->flags)) { + if (child->ifjoin_state + == PIM_IFJOIN_PRUNE_PENDING_TMP) + EVENT_OFF( + child->t_ifjoin_prune_pending_timer); + EVENT_OFF(child->t_ifjoin_expiry_timer); + PIM_IF_FLAG_UNSET_S_G_RPT(child->flags); + child->ifjoin_state = PIM_IFJOIN_NOINFO; + delete_on_noinfo(child); + } + } + + /* Received SG-RPT Prune delete oif from specific S,G */ + if (starg_ch && (msg_source_flags & PIM_RPT_BIT_MASK) + && !(msg_source_flags & PIM_WILDCARD_BIT_MASK)) { + struct pim_upstream *up = sg_ch->upstream; + PIM_IF_FLAG_SET_S_G_RPT(sg_ch->flags); + if (up) { + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: SGRpt flag is set, del inherit oif from up %s", + __func__, up->sg_str); + pim_channel_del_inherited_oif( + up->channel_oil, + starg_ch->interface, + __func__); + } + } + } + if (starg_ch && !filtered) + pim_ifchannel_set_star_g_join_state(starg_ch, 1, 0); + starg_ch = NULL; + } /* scan groups */ + + return 0; +} + +/* + * J/P Message Format + * + * While the RFC clearly states that this is 32 bits wide, it + * is cheating. These fields: + * Encoded-Unicast format (6 bytes MIN) + * Encoded-Group format (8 bytes MIN) + * Encoded-Source format (8 bytes MIN) + * are *not* 32 bits wide. + * + * Nor does the RFC explicitly call out the size for: + * Reserved (1 byte) + * Num Groups (1 byte) + * Holdtime (2 bytes) + * Number of Joined Sources (2 bytes) + * Number of Pruned Sources (2 bytes) + * + * This leads to a missleading representation from casual + * reading and making assumptions. Be careful! + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |PIM Ver| Type | Reserved | Checksum | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Upstream Neighbor Address (Encoded-Unicast format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Reserved | Num groups | Holdtime | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Multicast Group Address 1 (Encoded-Group format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of Joined Sources | Number of Pruned Sources | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Joined Source Address 1 (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . | + * | . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Joined Source Address n (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Pruned Source Address 1 (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . | + * | . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Pruned Source Address n (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Multicast Group Address m (Encoded-Group format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Number of Joined Sources | Number of Pruned Sources | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Joined Source Address 1 (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . | + * | . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Joined Source Address n (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Pruned Source Address 1 (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | . | + * | . | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | Pruned Source Address n (Encoded-Source format) | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + */ +int pim_joinprune_send(struct pim_rpf *rpf, struct list *groups) +{ + struct pim_jp_agg_group *group; + struct pim_interface *pim_ifp = NULL; + struct pim_jp_groups *grp = NULL; + struct pim_jp *msg = NULL; + struct listnode *node, *nnode; + uint8_t pim_msg[10000]; + uint8_t *curr_ptr = pim_msg; + bool new_packet = true; + size_t packet_left = 0; + size_t packet_size = 0; + size_t group_size = 0; + + if (rpf->source_nexthop.interface) + pim_ifp = rpf->source_nexthop.interface->info; + else { + zlog_warn("%s: RPF interface is not present", __func__); + return -1; + } + + + on_trace(__func__, rpf->source_nexthop.interface, rpf->rpf_addr); + + if (!pim_ifp) { + zlog_warn("%s: multicast not enabled on interface %s", __func__, + rpf->source_nexthop.interface->name); + return -1; + } + + if (pim_addr_is_any(rpf->rpf_addr)) { + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "%s: upstream=%pPA is myself on interface %s", + __func__, &rpf->rpf_addr, + rpf->source_nexthop.interface->name); + return 0; + } + + /* + RFC 4601: 4.3.1. Sending Hello Messages + + Thus, if a router needs to send a Join/Prune or Assert message on + an interface on which it has not yet sent a Hello message with the + currently configured IP address, then it MUST immediately send the + relevant Hello message without waiting for the Hello Timer to + expire, followed by the Join/Prune or Assert message. + */ + pim_hello_require(rpf->source_nexthop.interface); + + for (ALL_LIST_ELEMENTS(groups, node, nnode, group)) { + if (new_packet) { + msg = (struct pim_jp *)pim_msg; + + memset(msg, 0, sizeof(*msg)); + + pim_msg_addr_encode_ucast((uint8_t *)&msg->addr, + rpf->rpf_addr); + msg->reserved = 0; + msg->holdtime = htons(PIM_JP_HOLDTIME); + + new_packet = false; + + grp = &msg->groups[0]; + curr_ptr = (uint8_t *)grp; + packet_size = sizeof(struct pim_msg_header); + packet_size += sizeof(pim_encoded_unicast); + packet_size += + 4; // reserved (1) + groups (1) + holdtime (2) + + packet_left = rpf->source_nexthop.interface->mtu - 24; + packet_left -= packet_size; + } + if (PIM_DEBUG_PIM_J_P) + zlog_debug( + "%s: sending (G)=%pPAs to upstream=%pPA on interface %s", + __func__, &group->group, &rpf->rpf_addr, + rpf->source_nexthop.interface->name); + + group_size = pim_msg_get_jp_group_size(group->sources); + if (group_size > packet_left) { + pim_msg_build_header(pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, + packet_size, + PIM_MSG_TYPE_JOIN_PRUNE, false); + if (pim_msg_send(pim_ifp->pim_sock_fd, + pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, + packet_size, + rpf->source_nexthop.interface)) { + zlog_warn( + "%s: could not send PIM message on interface %s", + __func__, + rpf->source_nexthop.interface->name); + } + + msg = (struct pim_jp *)pim_msg; + memset(msg, 0, sizeof(*msg)); + + pim_msg_addr_encode_ucast((uint8_t *)&msg->addr, + rpf->rpf_addr); + msg->reserved = 0; + msg->holdtime = htons(PIM_JP_HOLDTIME); + + new_packet = false; + + grp = &msg->groups[0]; + curr_ptr = (uint8_t *)grp; + packet_size = sizeof(struct pim_msg_header); + packet_size += sizeof(pim_encoded_unicast); + packet_size += + 4; // reserved (1) + groups (1) + holdtime (2) + + packet_left = rpf->source_nexthop.interface->mtu - 24; + packet_left -= packet_size; + } + + msg->num_groups++; + /* + Build PIM message + */ + + curr_ptr += group_size; + packet_left -= group_size; + packet_size += group_size; + pim_msg_build_jp_groups(grp, group, group_size); + + if (!pim_ifp->pim_passive_enable) { + pim_ifp->pim_ifstat_join_send += ntohs(grp->joins); + pim_ifp->pim_ifstat_prune_send += ntohs(grp->prunes); + } + + if (PIM_DEBUG_PIM_TRACE) + zlog_debug( + "%s: interface %s num_joins %u num_prunes %u", + __func__, rpf->source_nexthop.interface->name, + ntohs(grp->joins), ntohs(grp->prunes)); + + grp = (struct pim_jp_groups *)curr_ptr; + if (packet_left < sizeof(struct pim_jp_groups) + || msg->num_groups == 255) { + pim_msg_build_header(pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, + packet_size, + PIM_MSG_TYPE_JOIN_PRUNE, false); + if (pim_msg_send(pim_ifp->pim_sock_fd, + pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, + packet_size, + rpf->source_nexthop.interface)) { + zlog_warn( + "%s: could not send PIM message on interface %s", + __func__, + rpf->source_nexthop.interface->name); + } + + new_packet = true; + } + } + + + if (!new_packet) { + // msg->num_groups = htons (msg->num_groups); + pim_msg_build_header( + pim_ifp->primary_address, qpim_all_pim_routers_addr, + pim_msg, packet_size, PIM_MSG_TYPE_JOIN_PRUNE, false); + if (pim_msg_send(pim_ifp->pim_sock_fd, pim_ifp->primary_address, + qpim_all_pim_routers_addr, pim_msg, + packet_size, rpf->source_nexthop.interface)) { + zlog_warn( + "%s: could not send PIM message on interface %s", + __func__, rpf->source_nexthop.interface->name); + } + } + return 0; +} |