From f182255c0f030761362560ee7342ae7627991efd Mon Sep 17 00:00:00 2001 From: Nathan Bahr Date: Tue, 17 Sep 2024 02:32:59 +0000 Subject: [PATCH 1/6] pimd: Add AutoRP functionality to PIMD Perform AutoRP discovery and candidate RP announcements using the AutoRP protocol. Mapping agent is not yet implemented, but this feature is not necessary for FRR to support AutoRP as we only need one AutoRP mapping agent in the network. Signed-off-by: Nathan Bahr --- pimd/pim_autorp.c | 1147 +++++++++++++++++++++++++++++++++++++++++++ pimd/pim_autorp.h | 158 ++++++ pimd/pim_iface.c | 15 + pimd/pim_instance.c | 8 + pimd/pim_instance.h | 3 + pimd/pim_rp.c | 5 +- pimd/pim_rp.h | 6 +- pimd/pim_sock.c | 30 ++ pimd/pim_sock.h | 2 + pimd/pimd.h | 4 + pimd/subdir.am | 2 + 11 files changed, 1374 insertions(+), 6 deletions(-) create mode 100644 pimd/pim_autorp.c create mode 100644 pimd/pim_autorp.h diff --git a/pimd/pim_autorp.c b/pimd/pim_autorp.c new file mode 100644 index 0000000000..cf0b9d5550 --- /dev/null +++ b/pimd/pim_autorp.c @@ -0,0 +1,1147 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pim_autorp.c: PIM AutoRP handling routines + * + * Copyright (C) 2024 ATCorp + * Nathan Bahr + */ + +#include + +#include +#include + +#include "lib/plist.h" +#include "lib/plist_int.h" +#include "lib/sockopt.h" +#include "lib/network.h" +#include "lib/termtable.h" +#include "lib/json.h" + +#include "pimd.h" +#include "pim_iface.h" +#include "pim_rp.h" +#include "pim_sock.h" +#include "pim_instance.h" +#include "pim_autorp.h" + +DEFINE_MTYPE_STATIC(PIMD, PIM_AUTORP, "PIM AutoRP info"); +DEFINE_MTYPE_STATIC(PIMD, PIM_AUTORP_RP, "PIM AutoRP advertised RP info"); +DEFINE_MTYPE_STATIC(PIMD, PIM_AUTORP_CRP, "PIM AutoRP candidate RP info"); +DEFINE_MTYPE_STATIC(PIMD, PIM_AUTORP_ANNOUNCE, "PIM AutoRP announcement packet"); + +static const char *PIM_AUTORP_ANNOUNCEMENT_GRP = "224.0.1.39"; +static const char *PIM_AUTORP_DISCOVERY_GRP = "224.0.1.40"; +static const in_port_t PIM_AUTORP_PORT = 496; + +static int pim_autorp_rp_cmp(const struct pim_autorp_rp *l, + const struct pim_autorp_rp *r) +{ + return pim_addr_cmp(l->addr, r->addr); +} + +DECLARE_SORTLIST_UNIQ(pim_autorp_rp, struct pim_autorp_rp, list, + pim_autorp_rp_cmp); + +static void pim_autorp_rp_free(struct pim_autorp_rp *rp) +{ + event_cancel(&rp->hold_timer); + + /* Clean up installed RP info */ + if (pim_rp_del(rp->autorp->pim, rp->addr, rp->grp, + (strlen(rp->grplist) ? rp->grplist : NULL), + RP_SRC_AUTORP)) + if (PIM_DEBUG_AUTORP) + zlog_err("%s: Failed to delete RP %pI4", __func__, + &rp->addr); + + XFREE(MTYPE_PIM_AUTORP_RP, rp); +} + +static void pim_autorp_rplist_free(struct pim_autorp_rp_head *head) +{ + struct pim_autorp_rp *rp; + + while ((rp = pim_autorp_rp_pop(head))) + pim_autorp_rp_free(rp); +} + +static void pim_autorp_rplist_cfree(struct pim_autorp_rp_head *head) +{ + struct pim_autorp_rp *rp; + + while ((rp = pim_autorp_rp_pop(head))) + XFREE(MTYPE_PIM_AUTORP_CRP, rp); +} + +static void pim_autorp_free(struct pim_autorp *autorp) +{ + pim_autorp_rplist_free(&(autorp->discovery_rp_list)); + pim_autorp_rp_fini(&(autorp->discovery_rp_list)); + + pim_autorp_rplist_cfree(&(autorp->candidate_rp_list)); + pim_autorp_rp_fini(&(autorp->candidate_rp_list)); +} + +static bool pim_autorp_join_groups(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct pim_instance *pim; + struct pim_autorp *autorp; + pim_addr grp; + + pim_ifp = ifp->info; + pim = pim_ifp->pim; + autorp = pim->autorp; + + inet_pton(PIM_AF, PIM_AUTORP_DISCOVERY_GRP, &grp); + if (pim_socket_join(autorp->sock, grp, pim_ifp->primary_address, + ifp->ifindex, pim_ifp)) { + zlog_err("Failed to join group %pI4 on interface %s", &grp, + ifp->name); + return false; + } + + /* TODO: Future Mapping agent implementation + * Join announcement group for AutoRP mapping agent + * inet_pton(PIM_AF, PIM_AUTORP_ANNOUNCEMENT_GRP, &grp); + * if (pim_socket_join(pim->autorp->sock, grp, + * pim_ifp->primary_address, + * ifp->ifindex, pim_ifp)) { + * zlog_err("Failed to join group %pI4 on interface %s", + * &grp, ifp->name); + * return errno; + * } + */ + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: Joined AutoRP groups on interface %s", __func__, + ifp->name); + + return true; +} + +static bool pim_autorp_leave_groups(struct interface *ifp) +{ + struct pim_interface *pim_ifp; + struct pim_instance *pim; + struct pim_autorp *autorp; + pim_addr grp; + + pim_ifp = ifp->info; + pim = pim_ifp->pim; + autorp = pim->autorp; + + inet_pton(PIM_AF, PIM_AUTORP_DISCOVERY_GRP, &grp); + if (pim_socket_leave(autorp->sock, grp, pim_ifp->primary_address, + ifp->ifindex, pim_ifp)) { + zlog_err("Failed to leave group %pI4 on interface %s", &grp, + ifp->name); + return false; + } + + /* TODO: Future Mapping agent implementation + * Leave announcement group for AutoRP mapping agent + * inet_pton(PIM_AF, PIM_AUTORP_ANNOUNCEMENT_GRP, &grp); + * if (pim_socket_leave(pim->autorp->sock, grp, + * pim_ifp->primary_address, + * ifp->ifindex, pim_ifp)) { + * zlog_err("Failed to leave group %pI4 on interface %s", + * &grp, ifp->name); + * return errno; + * } + */ + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: Left AutoRP groups on interface %s", __func__, + ifp->name); + + return true; +} + +static bool pim_autorp_setup(struct pim_autorp *autorp) +{ +#if defined(HAVE_IP_PKTINFO) + int data; + socklen_t data_len = sizeof(data); +#endif + + struct sockaddr_in autorp_addr = { .sin_family = AF_INET, + .sin_addr = { .s_addr = INADDR_ANY }, + .sin_port = htons(PIM_AUTORP_PORT) }; + + setsockopt_so_recvbuf(autorp->sock, 1024 * 1024 * 8); + +#if defined(HAVE_IP_PKTINFO) + /* Linux and Solaris IP_PKTINFO */ + data = 1; + if (setsockopt(autorp->sock, PIM_IPPROTO, IP_PKTINFO, &data, data_len)) { + zlog_err("Could not set IP_PKTINFO on socket fd=%d: errno=%d: %s", + autorp->sock, errno, safe_strerror(errno)); + return false; + } +#endif + + if (set_nonblocking(autorp->sock) < 0) { + zlog_err("Could not set non blocking on socket fd=%d: errno=%d: %s", + autorp->sock, errno, safe_strerror(errno)); + return false; + } + + if (sockopt_reuseaddr(autorp->sock)) { + zlog_err("Could not set reuse addr on socket fd=%d: errno=%d: %s", + autorp->sock, errno, safe_strerror(errno)); + return false; + } + + if (bind(autorp->sock, (const struct sockaddr *)&autorp_addr, + sizeof(autorp_addr)) < 0) { + zlog_err("Could not bind socket: %pSUp, fd=%d, errno=%d, %s", + (union sockunion *)&autorp_addr, autorp->sock, errno, + safe_strerror(errno)); + return false; + } + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP finished setup", __func__); + + return true; +} + +static bool pim_autorp_announcement(struct pim_autorp *autorp, uint8_t rpcnt, + uint16_t holdtime, char *buf, + size_t buf_size) +{ + /* TODO: Future Mapping agent implementation + * Implement AutoRP mapping agent logic using received announcement messages + */ + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP processed announcement message", + __func__); + return true; +} + +static void autorp_rp_holdtime(struct event *evt) +{ + /* RP hold time expired, remove the RP */ + struct pim_autorp_rp *rp = EVENT_ARG(evt); + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP hold time expired, RP removed: addr=%pI4, grp=%pFX, grplist=%s", + __func__, &rp->addr, &rp->grp, + (strlen(rp->grplist) ? rp->grplist : "NONE")); + + pim_autorp_rp_del(&(rp->autorp->discovery_rp_list), rp); + pim_autorp_rp_free(rp); +} + +static bool pim_autorp_add_rp(struct pim_autorp *autorp, pim_addr rpaddr, + struct prefix grp, char *listname, + uint16_t holdtime) +{ + struct pim_autorp_rp *rp; + struct pim_autorp_rp *trp = NULL; + + if (pim_rp_new(autorp->pim, rpaddr, grp, listname, RP_SRC_AUTORP)) { + zlog_err("%s: Failed to add new RP addr=%pI4, grp=%pFX, grplist=%s", + __func__, &rpaddr, &grp, + (listname ? listname : "NONE")); + return false; + } + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: Added new AutoRP learned RP addr=%pI4, grp=%pFX, grplist=%s", + __func__, &rpaddr, &grp, + (listname ? listname : "NONE")); + + rp = XCALLOC(MTYPE_PIM_AUTORP_RP, sizeof(*rp)); + rp->autorp = autorp; + memcpy(&(rp->addr), &rpaddr, sizeof(pim_addr)); + prefix_copy(&(rp->grp), &grp); + if (listname) + snprintf(rp->grplist, sizeof(rp->grplist), "%s", listname); + else + rp->grplist[0] = '\0'; + + rp->holdtime = holdtime; + rp->hold_timer = NULL; + trp = pim_autorp_rp_add(&(autorp->discovery_rp_list), rp); + if (trp == NULL) { + /* RP was brand new */ + trp = pim_autorp_rp_find(&(autorp->discovery_rp_list), + (const struct pim_autorp_rp *)rp); + } else { + /* RP already existed */ + XFREE(MTYPE_PIM_AUTORP_RP, rp); + event_cancel(&trp->hold_timer); + + /* We know the address matches, but these values may have changed */ + trp->holdtime = holdtime; + prefix_copy(&(trp->grp), &grp); + if (listname) { + snprintf(trp->grplist, sizeof(trp->grplist), "%s", + listname); + } else { + trp->grplist[0] = '\0'; + } + } + + if (holdtime > 0) { + event_add_timer(router->master, autorp_rp_holdtime, trp, + holdtime, &(trp->hold_timer)); + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: Started %u second hold timer for RP %pI4", + __func__, holdtime, &rp->addr); + } else { + /* If hold time is zero, make sure there doesn't exist a hold timer for it already */ + event_cancel(&trp->hold_timer); + } + + return true; +} + +static bool pim_autorp_discovery(struct pim_autorp *autorp, uint8_t rpcnt, + uint16_t holdtime, char *buf, size_t buf_size) +{ + int i, j; + struct autorp_pkt_rp *rp; + struct autorp_pkt_grp *grp; + size_t offset = 0; + pim_addr rp_addr; + struct prefix grppfix; + char plname[32]; + struct prefix_list *pl; + struct prefix_list_entry *ple; + int64_t seq = 1; + bool success = true; + + for (i = 0; i < rpcnt; ++i) { + if ((buf_size - offset) < AUTORP_RPLEN) + return false; + + rp = (struct autorp_pkt_rp *)(buf + offset); + offset += AUTORP_RPLEN; + + rp_addr.s_addr = rp->addr; + + /* Ignore RP's limited to PIM version 1 or with an unknown version */ + if (rp->pimver == PIM_V1 || rp->pimver == PIM_VUNKNOWN) { + zlog_warn("%s: Ignoring unsupported PIM version in AutoRP Discovery for RP %pI4", + __func__, (in_addr_t *)&(rp->addr)); + /* Update the offset to skip past the groups advertised for this RP */ + offset += (AUTORP_GRPLEN * rp->grpcnt); + continue; + } + + + if (rp->grpcnt == 0) { + /* No groups?? */ + zlog_warn("%s: Discovery message has no groups for RP %pI4", + __func__, (in_addr_t *)&(rp->addr)); + continue; + } + + if ((buf_size - offset) < AUTORP_GRPLEN) { + zlog_warn("%s: Buffer underrun parsing groups for RP %pI4", + __func__, (in_addr_t *)&(rp->addr)); + return false; + } + + grp = (struct autorp_pkt_grp *)(buf + offset); + offset += AUTORP_GRPLEN; + + if (rp->grpcnt == 1 && grp->negprefix == 0) { + /* Only one group with positive prefix, we can use the standard RP API */ + grppfix.family = AF_INET; + grppfix.prefixlen = grp->masklen; + grppfix.u.prefix4.s_addr = grp->addr; + if (!pim_autorp_add_rp(autorp, rp_addr, grppfix, NULL, + holdtime)) + success = false; + } else { + /* More than one grp, or the only group is a negative prefix, need to make a prefix list for this RP */ + snprintfrr(plname, sizeof(plname), "__AUTORP_%pI4__", + &rp_addr); + pl = prefix_list_get(AFI_IP, 0, plname); + + for (j = 0; j < rp->grpcnt; ++j) { + /* grp is already pointing at the first group in the buffer */ + ple = prefix_list_entry_new(); + ple->pl = pl; + ple->seq = seq; + seq += 5; + memset(&ple->prefix, 0, sizeof(ple->prefix)); + prefix_list_entry_update_start(ple); + ple->type = (grp->negprefix ? PREFIX_DENY + : PREFIX_PERMIT); + ple->prefix.family = AF_INET; + ple->prefix.prefixlen = grp->masklen; + ple->prefix.u.prefix4.s_addr = grp->addr; + ple->any = false; + ple->ge = 0; + ple->le = 32; + prefix_list_entry_update_finish(ple); + + if ((buf_size - offset) < AUTORP_GRPLEN) + return false; + + grp = (struct autorp_pkt_grp *)(buf + offset); + offset += AUTORP_GRPLEN; + } + + if (!pim_autorp_add_rp(autorp, rp_addr, grppfix, plname, + holdtime)) + success = false; + } + } + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: Processed AutoRP Discovery message", __func__); + + return success; +} + +static bool pim_autorp_msg(struct pim_autorp *autorp, char *buf, size_t buf_size) +{ + struct autorp_pkt_hdr *h; + + if (buf_size < AUTORP_HDRLEN) + return false; + + h = (struct autorp_pkt_hdr *)buf; + + if (h->version != AUTORP_VERSION) + return false; + + if (h->type == AUTORP_ANNOUNCEMENT_TYPE && + !pim_autorp_announcement(autorp, h->rpcnt, htons(h->holdtime), + buf + AUTORP_HDRLEN, + buf_size - AUTORP_HDRLEN)) + return false; + + if (h->type == AUTORP_DISCOVERY_TYPE && + !pim_autorp_discovery(autorp, h->rpcnt, htons(h->holdtime), + buf + AUTORP_HDRLEN, buf_size - AUTORP_HDRLEN)) + return false; + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: Processed AutoRP packet", __func__); + + return true; +} + +static void autorp_read(struct event *t); + +static void autorp_read_on(struct pim_autorp *autorp) +{ + event_add_read(router->master, autorp_read, autorp, autorp->sock, + &(autorp->read_event)); + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP socket read enabled", __func__); +} + +static void autorp_read_off(struct pim_autorp *autorp) +{ + event_cancel(&(autorp->read_event)); + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP socket read disabled", __func__); +} + +static void autorp_read(struct event *evt) +{ + struct pim_autorp *autorp = evt->arg; + int fd = evt->u.fd; + char buf[10000]; + int rd; + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: Reading from AutoRP socket", __func__); + + while (1) { + rd = pim_socket_recvfromto(fd, (uint8_t *)buf, sizeof(buf), + NULL, NULL, NULL, NULL, NULL); + if (rd <= 0) { + if (errno == EINTR) + continue; + if (errno == EWOULDBLOCK || errno == EAGAIN) + break; + + zlog_warn("%s: Failure reading rd=%d: fd=%d: errno=%d: %s", + __func__, rd, fd, errno, safe_strerror(errno)); + goto err; + } + + if (!pim_autorp_msg(autorp, buf, rd)) + zlog_err("%s: Failure parsing AutoRP message", __func__); + /* Keep reading until would block */ + } + + /* No error, enable read again */ + autorp_read_on(autorp); + +err: + return; +} + +static bool pim_autorp_socket_enable(struct pim_autorp *autorp) +{ + int fd; + + frr_with_privs (&pimd_privs) { + fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); + if (fd < 0) { + zlog_warn("Could not create autorp socket: errno=%d: %s", + errno, safe_strerror(errno)); + return false; + } + + autorp->sock = fd; + if (!pim_autorp_setup(autorp)) { + zlog_warn("Could not setup autorp socket fd=%d: errno=%d: %s", + fd, errno, safe_strerror(errno)); + close(fd); + autorp->sock = -1; + return false; + } + } + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP socket enabled", __func__); + + return true; +} + +static bool pim_autorp_socket_disable(struct pim_autorp *autorp) +{ + if (close(autorp->sock)) { + zlog_warn("Failure closing autorp socket: fd=%d errno=%d: %s", + autorp->sock, errno, safe_strerror(errno)); + return false; + } + + autorp_read_off(autorp); + autorp->sock = -1; + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP socket disabled", __func__); + + return true; +} + +static void autorp_send_announcement(struct event *evt) +{ + struct pim_autorp *autorp = EVENT_ARG(evt); + struct interface *ifp; + struct pim_interface *pim_ifp; + struct sockaddr_in announceGrp; + + announceGrp.sin_family = AF_INET; + announceGrp.sin_port = htons(PIM_AUTORP_PORT); + inet_pton(PIM_AF, PIM_AUTORP_ANNOUNCEMENT_GRP, &announceGrp.sin_addr); + + if (autorp->annouce_pkt_sz >= MIN_AUTORP_PKT_SZ) { + setsockopt(autorp->sock, IPPROTO_IP, IP_MULTICAST_TTL, + &(autorp->announce_scope), + sizeof(autorp->announce_scope)); + + FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) { + pim_ifp = ifp->info; + /* Only send on active interfaces with full pim enabled, non-passive + * and have a primary address set. + */ + if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE) && + pim_ifp && pim_ifp->pim_enable && + !pim_ifp->pim_passive_enable && + !pim_addr_is_any(pim_ifp->primary_address)) { + setsockopt(autorp->sock, IPPROTO_IP, + IP_MULTICAST_IF, + &(pim_ifp->primary_address), + sizeof(pim_ifp->primary_address)); + sendto(autorp->sock, autorp->annouce_pkt, + autorp->annouce_pkt_sz, 0, + (struct sockaddr *)&announceGrp, + sizeof(announceGrp)); + } + } + } + + /* Start the new timer for the entire announce interval */ + event_add_timer(router->master, autorp_send_announcement, autorp, + autorp->announce_interval, &(autorp->announce_timer)); +} + +static void autorp_announcement_on(struct pim_autorp *autorp) +{ + int interval = 5; + + if (interval > autorp->announce_interval) { + /* If the configured interval is less than 5 seconds, then just use that */ + interval = autorp->announce_interval; + } + event_add_timer(router->master, autorp_send_announcement, autorp, + interval, &(autorp->announce_timer)); + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP announcement sending enabled", __func__); +} + +static void autorp_announcement_off(struct pim_autorp *autorp) +{ + event_cancel(&(autorp->announce_timer)); + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP announcement sending disabled", __func__); +} + +/* Pack the groups of the RP + * rp - Pointer to the RP + * buf - Pointer to the buffer where to start packing groups + * returns - Total group count packed + */ +static uint8_t pim_autorp_new_announcement_rp_grps(struct pim_autorp_rp *rp, + uint8_t *buf) +{ + struct prefix_list *plist; + struct prefix_list_entry *ple; + struct autorp_pkt_grp *grpp = (struct autorp_pkt_grp *)buf; + uint8_t cnt = 0; + in_addr_t taddr; + + if (is_default_prefix(&(rp->grp))) { + /* No group so pack from the prefix list + * The grplist should be set and the prefix list exist with at least one group address + */ + plist = prefix_list_lookup(AFI_IP, rp->grplist); + for (ple = plist->head; ple; ple = ple->next) { + taddr = ntohl(ple->prefix.u.prefix4.s_addr); + if ((taddr & 0xF0000000) == 0xE0000000) { + grpp->addr = ple->prefix.u.prefix4.s_addr; + grpp->masklen = ple->prefix.prefixlen; + grpp->negprefix = + (ple->type == PREFIX_PERMIT ? 0 : 1); + grpp->reserved = 0; + + ++cnt; + grpp = (struct autorp_pkt_grp + *)(buf + + (sizeof(struct autorp_pkt_grp) * + cnt)); + } + } + + return cnt; + } + + /* Only one of group or prefix list should be defined */ + grpp->addr = rp->grp.u.prefix4.s_addr; + grpp->masklen = rp->grp.prefixlen; + grpp->negprefix = 0; + grpp->reserved = 0; + return 1; +} + +/* Pack a single candidate RP + * rp - Pointer to the RP to pack + * buf - Pointer to the buffer where to start packing the RP + * returns - Buffer pointer pointing to the start of the next RP + */ +static uint8_t *pim_autorp_new_announcement_rp(struct pim_autorp_rp *rp, + uint8_t *buf) +{ + struct autorp_pkt_rp *brp = (struct autorp_pkt_rp *)buf; + + /* Since this is an in_addr, assume it's already the right byte order */ + brp->addr = rp->addr.s_addr; + brp->pimver = PIM_V2; + brp->reserved = 0; + brp->grpcnt = + pim_autorp_new_announcement_rp_grps(rp, + buf + sizeof(struct autorp_pkt_rp)); + return buf + sizeof(struct autorp_pkt_rp) + + (brp->grpcnt * sizeof(struct autorp_pkt_grp)); +} + +/* Pack the candidate RP's on the announcement packet + * autorp - Pointer to the AutoRP instance + * buf - Pointer to the buffer where to start packing the first RP + * bufsz - Output parameter to track size of packed bytes + * returns - Total count of RP's packed + */ +static int pim_autorp_new_announcement_rps(struct pim_autorp *autorp, + uint8_t *buf, uint16_t *bufsz) +{ + int cnt = 0; + struct pim_autorp_rp *rp; + /* Keep the original buffer pointer to calculate final size after packing */ + uint8_t *obuf = buf; + struct prefix_list *plist; + struct prefix_list_entry *ple; + in_addr_t taddr; + + frr_each_safe (pim_autorp_rp, &(autorp->candidate_rp_list), rp) { + /* We must have an rp address and either group or list in order to pack this RP, so skip this one */ + if (pim_addr_is_any(rp->addr) || + (is_default_prefix(&(rp->grp)) && strlen(rp->grplist) == 0)) + continue; + + /* Group is net set, so list must be set, make sure the prefix list exists and has valid multicast groups */ + if (is_default_prefix(&(rp->grp))) { + plist = prefix_list_lookup(AFI_IP, rp->grplist); + if (plist == NULL) + continue; + plist = prefix_list_lookup(AFI_IP, rp->grplist); + for (ple = plist->head; ple; ple = ple->next) { + taddr = ntohl(ple->prefix.u.prefix4.s_addr); + if ((taddr & 0xF0000000) == 0xE0000000) + break; + } + + /* If we went through the entire list without finding a multicast prefix, then skip this RP */ + if (ple == NULL) + continue; + } + + /* Now we know for sure we will pack this RP, so count it */ + ++cnt; + /* This will return the buffer pointer at the location to start packing the next RP */ + buf = pim_autorp_new_announcement_rp(rp, buf); + } + + if (cnt > 0) + *bufsz = buf - obuf; + + return cnt; +} + +/* Build the new announcement packet. If there is a packet to send, restart the send timer with a short wait */ +static void pim_autorp_new_announcement(struct pim_instance *pim) +{ + struct pim_autorp *autorp = pim->autorp; + struct autorp_pkt_hdr *hdr; + int32_t holdtime; + + /* First disable any existing send timer */ + autorp_announcement_off(autorp); + + if (!autorp->annouce_pkt) { + /* + * First time building, allocate the space + * Allocate the max packet size of 65536 so we don't need to resize later. + * This should be ok since we are only allocating the memory once for a single packet (potentially per vrf) + */ + autorp->annouce_pkt = XCALLOC(MTYPE_PIM_AUTORP_ANNOUNCE, 65536); + } + + autorp->annouce_pkt_sz = 0; + + holdtime = autorp->announce_holdtime; + if (holdtime == DEFAULT_ANNOUNCE_HOLDTIME) + holdtime = autorp->announce_interval * 3; + if (holdtime > UINT16_MAX) + holdtime = UINT16_MAX; + + hdr = (struct autorp_pkt_hdr *)autorp->annouce_pkt; + hdr->version = AUTORP_VERSION; + hdr->type = AUTORP_ANNOUNCEMENT_TYPE; + hdr->holdtime = htons((uint16_t)holdtime); + hdr->reserved = 0; + hdr->rpcnt = + pim_autorp_new_announcement_rps(autorp, + autorp->annouce_pkt + + sizeof(struct autorp_pkt_hdr), + &(autorp->annouce_pkt_sz)); + + /* Still need to add on the size of the header */ + autorp->annouce_pkt_sz += sizeof(struct autorp_pkt_hdr); + + /* Only turn on the announcement timer if we have a packet to send */ + if (autorp->annouce_pkt_sz >= MIN_AUTORP_PKT_SZ) + autorp_announcement_on(autorp); +} + +bool pim_autorp_rm_candidate_rp(struct pim_instance *pim, pim_addr rpaddr) +{ + struct pim_autorp *autorp = pim->autorp; + struct pim_autorp_rp *rp; + struct pim_autorp_rp find = { .addr = rpaddr }; + + rp = pim_autorp_rp_find(&(autorp->candidate_rp_list), + (const struct pim_autorp_rp *)&find); + if (!rp) + return false; + + pim_autorp_rp_del(&(autorp->candidate_rp_list), rp); + pim_autorp_rp_free(rp); + pim_autorp_new_announcement(pim); + return true; +} + +void pim_autorp_add_candidate_rp_group(struct pim_instance *pim, + pim_addr rpaddr, struct prefix group) +{ + struct pim_autorp *autorp = pim->autorp; + struct pim_autorp_rp *rp; + struct pim_autorp_rp find = { .addr = rpaddr }; + + rp = pim_autorp_rp_find(&(autorp->candidate_rp_list), + (const struct pim_autorp_rp *)&find); + if (!rp) { + rp = XCALLOC(MTYPE_PIM_AUTORP_CRP, sizeof(*rp)); + memset(rp, 0, sizeof(struct pim_autorp_rp)); + rp->autorp = autorp; + memcpy(&(rp->addr), &rpaddr, sizeof(pim_addr)); + pim_autorp_rp_add(&(autorp->candidate_rp_list), rp); + } + + apply_mask(&group); + prefix_copy(&(rp->grp), &group); + /* A new group prefix implies that any previous prefix list is now invalid */ + rp->grplist[0] = '\0'; + + pim_autorp_new_announcement(pim); +} + +bool pim_autorp_rm_candidate_rp_group(struct pim_instance *pim, pim_addr rpaddr, + struct prefix group) +{ + struct pim_autorp *autorp = pim->autorp; + struct pim_autorp_rp *rp; + struct pim_autorp_rp find = { .addr = rpaddr }; + + rp = pim_autorp_rp_find(&(autorp->candidate_rp_list), + (const struct pim_autorp_rp *)&find); + if (!rp) + return false; + + memset(&(rp->grp), 0, sizeof(rp->grp)); + pim_autorp_new_announcement(pim); + return true; +} + +void pim_autorp_add_candidate_rp_plist(struct pim_instance *pim, + pim_addr rpaddr, const char *plist) +{ + struct pim_autorp *autorp = pim->autorp; + struct pim_autorp_rp *rp; + struct pim_autorp_rp find = { .addr = rpaddr }; + + rp = pim_autorp_rp_find(&(autorp->candidate_rp_list), + (const struct pim_autorp_rp *)&find); + if (!rp) { + rp = XCALLOC(MTYPE_PIM_AUTORP_CRP, sizeof(*rp)); + memset(rp, 0, sizeof(struct pim_autorp_rp)); + rp->autorp = autorp; + memcpy(&(rp->addr), &rpaddr, sizeof(pim_addr)); + pim_autorp_rp_add(&(autorp->candidate_rp_list), rp); + } + + snprintf(rp->grplist, sizeof(rp->grplist), "%s", plist); + /* A new group prefix list implies that any previous group prefix is now invalid */ + memset(&(rp->grp), 0, sizeof(rp->grp)); + + pim_autorp_new_announcement(pim); +} + +bool pim_autorp_rm_candidate_rp_plist(struct pim_instance *pim, pim_addr rpaddr, + const char *plist) +{ + struct pim_autorp *autorp = pim->autorp; + struct pim_autorp_rp *rp; + struct pim_autorp_rp find = { .addr = rpaddr }; + + rp = pim_autorp_rp_find(&(autorp->candidate_rp_list), + (const struct pim_autorp_rp *)&find); + if (!rp) + return false; + + rp->grplist[0] = '\0'; + pim_autorp_new_announcement(pim); + return true; +} + +void pim_autorp_announce_scope(struct pim_instance *pim, uint8_t scope) +{ + struct pim_autorp *autorp = pim->autorp; + + scope = (scope == 0 ? DEFAULT_ANNOUNCE_SCOPE : scope); + if (autorp->announce_scope != scope) { + autorp->announce_scope = scope; + pim_autorp_new_announcement(pim); + } +} + +void pim_autorp_announce_interval(struct pim_instance *pim, uint16_t interval) +{ + struct pim_autorp *autorp = pim->autorp; + + interval = (interval == 0 ? DEFAULT_ANNOUNCE_INTERVAL : interval); + if (autorp->announce_interval != interval) { + autorp->announce_interval = interval; + pim_autorp_new_announcement(pim); + } +} + +void pim_autorp_announce_holdtime(struct pim_instance *pim, int32_t holdtime) +{ + struct pim_autorp *autorp = pim->autorp; + + if (autorp->announce_holdtime != holdtime) { + autorp->announce_holdtime = holdtime; + pim_autorp_new_announcement(pim); + } +} + +void pim_autorp_add_ifp(struct interface *ifp) +{ + /* Add a new interface for autorp + * When autorp is enabled, we must join the autorp groups on all + * pim/multicast interfaces. When autorp first starts, if finds all + * current multicast interfaces and joins on them. If a new interface + * comes up or is configured for multicast after autorp is running, then + * this method will add it for autorp-> + * This is called even when adding a new pim interface that is not yet + * active, so make sure the check, it'll call in again once the interface is up. + */ + struct pim_instance *pim; + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE) && pim_ifp && + pim_ifp->pim_enable) { + pim = pim_ifp->pim; + if (pim && pim->autorp && pim->autorp->do_discovery) { + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: Adding interface %s to AutoRP, joining AutoRP groups", + __func__, ifp->name); + if (!pim_autorp_join_groups(ifp)) { + zlog_err("Could not join AutoRP groups, errno=%d, %s", + errno, safe_strerror(errno)); + } + } + } +} + +void pim_autorp_rm_ifp(struct interface *ifp) +{ + /* Remove interface for autorp + * When an interface is no longer enabled for multicast, or at all, then + * we should leave the AutoRP groups on this interface. + */ + struct pim_instance *pim; + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE) && pim_ifp) { + pim = pim_ifp->pim; + if (pim && pim->autorp) { + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: Removing interface %s from AutoRP, leaving AutoRP groups", + __func__, ifp->name); + if (!pim_autorp_leave_groups(ifp)) { + zlog_err("Could not leave AutoRP groups, errno=%d, %s", + errno, safe_strerror(errno)); + } + } + } +} + +void pim_autorp_start_discovery(struct pim_instance *pim) +{ + struct interface *ifp; + struct pim_autorp *autorp = pim->autorp; + + if (!autorp->do_discovery) { + autorp->do_discovery = true; + autorp_read_on(autorp); + + FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) { + pim_autorp_add_ifp(ifp); + } + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP Discovery started", __func__); + } +} + +void pim_autorp_stop_discovery(struct pim_instance *pim) +{ + struct interface *ifp; + struct pim_autorp *autorp = pim->autorp; + + if (autorp->do_discovery) { + FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) { + pim_autorp_rm_ifp(ifp); + } + + autorp->do_discovery = false; + autorp_read_off(autorp); + + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP Discovery stopped", __func__); + } +} + +void pim_autorp_init(struct pim_instance *pim) +{ + struct pim_autorp *autorp; + + autorp = XCALLOC(MTYPE_PIM_AUTORP, sizeof(*autorp)); + autorp->pim = pim; + autorp->sock = -1; + autorp->read_event = NULL; + autorp->announce_timer = NULL; + autorp->do_discovery = false; + pim_autorp_rp_init(&(autorp->discovery_rp_list)); + pim_autorp_rp_init(&(autorp->candidate_rp_list)); + autorp->announce_scope = DEFAULT_ANNOUNCE_SCOPE; + autorp->announce_interval = DEFAULT_ANNOUNCE_INTERVAL; + autorp->announce_holdtime = DEFAULT_ANNOUNCE_HOLDTIME; + + if (!pim_autorp_socket_enable(autorp)) { + zlog_err("%s: AutoRP failed to initialize", __func__); + return; + } + + pim->autorp = autorp; + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP Initialized", __func__); + + /* Start AutoRP discovery by default on startup */ + pim_autorp_start_discovery(pim); +} + +void pim_autorp_finish(struct pim_instance *pim) +{ + struct pim_autorp *autorp = pim->autorp; + + autorp_read_off(autorp); + pim_autorp_free(autorp); + if (pim_autorp_socket_disable(autorp)) { + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP Finished", __func__); + } else + zlog_err("%s: AutoRP failed to finish", __func__); + + XFREE(MTYPE_PIM_AUTORP, pim->autorp); +} + +int pim_autorp_config_write(struct pim_instance *pim, struct vty *vty) +{ + struct pim_autorp_rp *rp; + struct pim_autorp *autorp = pim->autorp; + char interval_str[16] = { 0 }; + char scope_str[16] = { 0 }; + char holdtime_str[32] = { 0 }; + char grp_str[64] = { 0 }; + int writes = 0; + + if (!autorp->do_discovery) { + vty_out(vty, " no autorp discovery\n"); + ++writes; + } + + if (autorp->announce_interval != DEFAULT_ANNOUNCE_INTERVAL) { + snprintf(interval_str, sizeof(interval_str), " interval %u", + autorp->announce_interval); + } + + if (autorp->announce_scope != DEFAULT_ANNOUNCE_SCOPE) { + snprintf(scope_str, sizeof(scope_str), " scope %u", + autorp->announce_scope); + } + + if (autorp->announce_holdtime != DEFAULT_ANNOUNCE_HOLDTIME) { + snprintf(holdtime_str, sizeof(holdtime_str), " holdtime %u", + autorp->announce_holdtime); + } + + if (strlen(interval_str) || strlen(scope_str) || strlen(holdtime_str)) { + vty_out(vty, " autorp announce%s%s%s\n", interval_str, + scope_str, holdtime_str); + ++writes; + } + + frr_each_safe (pim_autorp_rp, &(autorp->candidate_rp_list), rp) { + /* Only print candidate RP's that have all the information needed to be announced */ + if (pim_addr_is_any(rp->addr) || + (is_default_prefix(&(rp->grp)) && strlen(rp->grplist) == 0)) + continue; + + /* Don't make sure the prefix list has multicast groups, user may not have created it yet */ + + if (!is_default_prefix(&(rp->grp))) + snprintfrr(grp_str, sizeof(grp_str), "%pFX", &(rp->grp)); + else + snprintfrr(grp_str, sizeof(grp_str), "group-list %s", + rp->grplist); + + vty_out(vty, " autorp announce %pI4 %s\n", &(rp->addr), grp_str); + ++writes; + } + + return writes; +} + +void pim_autorp_show_autorp(struct vty *vty, struct pim_instance *pim, + json_object *json) +{ + struct pim_autorp_rp *rp; + struct pim_autorp *autorp = pim->autorp; + struct ttable *tt = NULL; + char *table = NULL; + char grp_str[64] = { 0 }; + char plist_str[64] = { 0 }; + json_object *annouce_jobj; + + /* Prepare table. */ + tt = ttable_new(&ttable_styles[TTSTYLE_BLANK]); + ttable_add_row(tt, "RP address|group|prefix-list"); + tt->style.cell.rpad = 2; + tt->style.corner = '+'; + ttable_restyle(tt); + + frr_each_safe (pim_autorp_rp, &(autorp->candidate_rp_list), rp) { + if (!is_default_prefix(&(rp->grp))) + snprintfrr(grp_str, sizeof(grp_str), "%pFX", &(rp->grp)); + else + snprintfrr(plist_str, sizeof(plist_str), "%s", + rp->grplist); + + ttable_add_row(tt, "%pI4|%s|%s", &(rp->addr), grp_str, + plist_str); + } + + if (json) { + json_object_boolean_add(json, "discoveryEnabled", + autorp->do_discovery); + + annouce_jobj = json_object_new_object(); + json_object_int_add(annouce_jobj, "scope", + autorp->announce_scope); + json_object_int_add(annouce_jobj, "interval", + autorp->announce_interval); + json_object_int_add(annouce_jobj, "holdtime", + autorp->announce_holdtime); + json_object_object_add(annouce_jobj, "rpList", + ttable_json_with_json_text( + tt, "sss", + "rpAddress|group|prefixList")); + + json_object_object_add(json, "announce", annouce_jobj); + } else { + vty_out(vty, "AutoRP Discovery is %sabled\n", + (autorp->do_discovery ? "en" : "dis")); + vty_out(vty, "AutoRP Candidate RPs\n"); + vty_out(vty, " interval %us, scope %u, holdtime %us\n", + autorp->announce_interval, autorp->announce_scope, + (autorp->announce_holdtime == DEFAULT_ANNOUNCE_HOLDTIME + ? (autorp->announce_interval * 3) + : autorp->announce_holdtime)); + + vty_out(vty, "\n"); + + table = ttable_dump(tt, "\n"); + vty_out(vty, "%s\n", table); + XFREE(MTYPE_TMP, table); + } + + ttable_del(tt); +} diff --git a/pimd/pim_autorp.h b/pimd/pim_autorp.h new file mode 100644 index 0000000000..a0b029d00a --- /dev/null +++ b/pimd/pim_autorp.h @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * pim_autorp.h: PIM Auto RP handling related + * + * Copyright (C) 20224 ATCorp. + * Nathan Bahr + */ + +#ifndef __PIM_AUTORP_H__ +#define __PIM_AUTORP_H__ + +#include + +#define AUTORP_VERSION 1 +#define AUTORP_ANNOUNCEMENT_TYPE 1 +#define AUTORP_DISCOVERY_TYPE 2 +#define PIM_VUNKNOWN 0 +#define PIM_V1 1 +#define PIM_V2 2 +#define PIM_V1_2 3 + +#define DEFAULT_ANNOUNCE_INTERVAL 60 +#define DEFAULT_ANNOUNCE_SCOPE 31 +#define DEFAULT_ANNOUNCE_HOLDTIME -1 + +PREDECL_SORTLIST_UNIQ(pim_autorp_rp); + +struct autorp_pkt_grp { +#if __BYTE_ORDER == __LITTLE_ENDIAN + uint8_t negprefix : 1; + uint8_t reserved : 7; +#elif __BYTE_ORDER == __BIG_ENDIAN + uint8_t reserved : 7; + uint8_t negprefix : 1; +#else +#error "Please fix " +#endif + uint8_t masklen; + uint32_t addr; +} __attribute__((__packed__)); + +struct autorp_pkt_rp { + uint32_t addr; +#if __BYTE_ORDER == __LITTLE_ENDIAN + uint8_t pimver : 2; + uint8_t reserved : 6; +#elif __BYTE_ORDER == __BIG_ENDIAN + uint8_t reserved : 6; + uint8_t pimver : 2; +#else +#error "Please fix " +#endif + uint8_t grpcnt; +} __attribute__((__packed__)); + +struct autorp_pkt_hdr { +#if __BYTE_ORDER == __LITTLE_ENDIAN + uint8_t type : 4; + uint8_t version : 4; +#elif __BYTE_ORDER == __BIG_ENDIAN + uint8_t version : 4; + uint8_t type : 4; +#else +#error "Please fix " +#endif + uint8_t rpcnt; + uint16_t holdtime; + uint32_t reserved; +} __attribute__((__packed__)); + +#define MIN_AUTORP_PKT_SZ \ + (sizeof(struct autorp_pkt_hdr) + sizeof(struct autorp_pkt_rp) + \ + sizeof(struct autorp_pkt_grp)) + +struct pim_autorp_rp { + struct pim_autorp *autorp; + struct in_addr addr; + uint16_t holdtime; + struct event *hold_timer; + struct prefix grp; + char grplist[32]; + struct pim_autorp_rp_item list; +}; + +struct pim_autorp { + /* backpointer to pim instance */ + struct pim_instance *pim; + + /* UDP socket bound to AutoRP port, used for sending and receiving all AutoRP packets */ + int sock; + + /* Event for reading AutoRP packets */ + struct event *read_event; + + /* Event for sending announcement packets */ + struct event *announce_timer; + + /* Event for sending discovery packets*/ + /* struct event *discovery_timer; */ + + /* Flag enabling reading discovery packets */ + bool do_discovery; + + /* Flag enabling mapping agent (reading announcements and sending discovery)*/ + /* bool do_mapping; */ + + /* List of RP's in received discovery packets */ + struct pim_autorp_rp_head discovery_rp_list; + + /* List of configured candidate RP's to send in announcement packets */ + struct pim_autorp_rp_head candidate_rp_list; + + /* List of announced RP's to send in discovery packets */ + /* struct pim_autorp_rp_head mapping_rp_list; */ + + /* Packet parameters for sending announcement packets */ + uint8_t announce_scope; + uint16_t announce_interval; + int32_t announce_holdtime; + + /* Pre-built announcement packet, only changes when configured RP's or packet parameters change */ + uint8_t *annouce_pkt; + uint16_t annouce_pkt_sz; + + /* TODO: Packet parameters for sending discovery packets + * int discovery_scope; + * int discovery_interval; + * int discovery_holdtime; + */ +}; + +#define AUTORP_GRPLEN 6 +#define AUTORP_RPLEN 6 +#define AUTORP_HDRLEN 8 + +bool pim_autorp_rm_candidate_rp(struct pim_instance *pim, pim_addr rpaddr); +void pim_autorp_add_candidate_rp_group(struct pim_instance *pim, + pim_addr rpaddr, struct prefix group); +bool pim_autorp_rm_candidate_rp_group(struct pim_instance *pim, pim_addr rpaddr, + struct prefix group); +void pim_autorp_add_candidate_rp_plist(struct pim_instance *pim, + pim_addr rpaddr, const char *plist); +bool pim_autorp_rm_candidate_rp_plist(struct pim_instance *pim, pim_addr rpaddr, + const char *plist); +void pim_autorp_announce_scope(struct pim_instance *pim, uint8_t scope); +void pim_autorp_announce_interval(struct pim_instance *pim, uint16_t interval); +void pim_autorp_announce_holdtime(struct pim_instance *pim, int32_t holdtime); +void pim_autorp_add_ifp(struct interface *ifp); +void pim_autorp_rm_ifp(struct interface *ifp); +void pim_autorp_start_discovery(struct pim_instance *pim); +void pim_autorp_stop_discovery(struct pim_instance *pim); +void pim_autorp_init(struct pim_instance *pim); +void pim_autorp_finish(struct pim_instance *pim); +int pim_autorp_config_write(struct pim_instance *pim, struct vty *vty); +void pim_autorp_show_autorp(struct vty *vty, struct pim_instance *pim, + json_object *json); + +#endif diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c index 7f873f45dc..1dc9307e4f 100644 --- a/pimd/pim_iface.c +++ b/pimd/pim_iface.c @@ -1914,6 +1914,12 @@ static int pim_ifp_up(struct interface *ifp) } } +#if PIM_IPV == 4 + if (pim->autorp && pim->autorp->do_discovery && pim_ifp && + pim_ifp->pim_enable) + pim_autorp_add_ifp(ifp); +#endif + pim_cand_addrs_changed(); return 0; } @@ -1951,6 +1957,10 @@ static int pim_ifp_down(struct interface *ifp) pim_ifstat_reset(ifp); } +#if PIM_IPV == 4 + pim_autorp_rm_ifp(ifp); +#endif + pim_cand_addrs_changed(); return 0; } @@ -2023,6 +2033,11 @@ void pim_pim_interface_delete(struct interface *ifp) if (!pim_ifp) return; +#if PIM_IPV == 4 + if (pim_ifp->pim_enable) + pim_autorp_rm_ifp(ifp); +#endif + pim_ifp->pim_enable = false; pim_if_membership_clear(ifp); diff --git a/pimd/pim_instance.c b/pimd/pim_instance.c index 9a697c9209..f7c5ea3bcf 100644 --- a/pimd/pim_instance.c +++ b/pimd/pim_instance.c @@ -57,6 +57,10 @@ static void pim_instance_terminate(struct pim_instance *pim) pim_mroute_socket_disable(pim); +#if PIM_IPV == 4 + pim_autorp_finish(pim); +#endif + XFREE(MTYPE_PIM_PLIST_NAME, pim->spt.plist); XFREE(MTYPE_PIM_PLIST_NAME, pim->register_plist); @@ -125,6 +129,10 @@ static struct pim_instance *pim_instance_init(struct vrf *vrf) pim->msdp.keep_alive = PIM_MSDP_PEER_KA_TIME; pim->msdp.connection_retry = PIM_MSDP_PEER_CONNECT_RETRY_TIME; +#if PIM_IPV == 4 + pim_autorp_init(pim); +#endif + return pim; } diff --git a/pimd/pim_instance.h b/pimd/pim_instance.h index ec331332cf..f484d847b2 100644 --- a/pimd/pim_instance.h +++ b/pimd/pim_instance.h @@ -17,6 +17,7 @@ #include "pim_oil.h" #include "pim_upstream.h" #include "pim_mroute.h" +#include "pim_autorp.h" enum pim_spt_switchover { PIM_SPT_IMMEDIATE, @@ -152,6 +153,8 @@ struct pim_instance { struct pim_msdp msdp; struct pim_vxlan_instance vxlan; + struct pim_autorp *autorp; + struct list *ssmpingd_list; pim_addr ssmpingd_group_addr; diff --git a/pimd/pim_rp.c b/pimd/pim_rp.c index a2ddc82164..0c47bc1582 100644 --- a/pimd/pim_rp.c +++ b/pimd/pim_rp.c @@ -1140,7 +1140,8 @@ int pim_rp_config_write(struct pim_instance *pim, struct vty *vty) if (pim_rpf_addr_is_inaddr_any(&rp_info->rp)) continue; - if (rp_info->rp_src == RP_SRC_BSR) + if (rp_info->rp_src != RP_SRC_NONE && + rp_info->rp_src != RP_SRC_STATIC) continue; rp_addr = rp_info->rp.rpf_addr; @@ -1200,6 +1201,8 @@ void pim_rp_show_information(struct pim_instance *pim, struct prefix *range, strlcpy(source, "Static", sizeof(source)); else if (rp_info->rp_src == RP_SRC_BSR) strlcpy(source, "BSR", sizeof(source)); + else if (rp_info->rp_src == RP_SRC_AUTORP) + strlcpy(source, "AutoRP", sizeof(source)); else strlcpy(source, "None", sizeof(source)); if (json) { diff --git a/pimd/pim_rp.h b/pimd/pim_rp.h index 32c6306740..24832d0dbd 100644 --- a/pimd/pim_rp.h +++ b/pimd/pim_rp.h @@ -16,11 +16,7 @@ struct pim_interface; -enum rp_source { - RP_SRC_NONE = 0, - RP_SRC_STATIC, - RP_SRC_BSR -}; +enum rp_source { RP_SRC_NONE = 0, RP_SRC_STATIC, RP_SRC_BSR, RP_SRC_AUTORP }; struct rp_info { struct prefix group; diff --git a/pimd/pim_sock.c b/pimd/pim_sock.c index 3476c177b7..8900652df9 100644 --- a/pimd/pim_sock.c +++ b/pimd/pim_sock.c @@ -292,6 +292,36 @@ int pim_socket_join(int fd, pim_addr group, pim_addr ifaddr, ifindex_t ifindex, return ret; } +int pim_socket_leave(int fd, pim_addr group, pim_addr ifaddr, ifindex_t ifindex, + struct pim_interface *pim_ifp) +{ + int ret; + +#if PIM_IPV == 4 + ret = setsockopt_ipv4_multicast(fd, IP_DROP_MEMBERSHIP, ifaddr, + group.s_addr, ifindex); +#else + struct ipv6_mreq opt; + + memcpy(&opt.ipv6mr_multiaddr, &group, 16); + opt.ipv6mr_interface = ifindex; + ret = setsockopt(fd, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &opt, sizeof(opt)); +#endif + + if (ret) { + flog_err(EC_LIB_SOCKET, + "Failure socket leaving fd=%d group %pPAs on interface address %pPAs: %m", + fd, &group, &ifaddr); + pim_ifp->igmp_ifstat_joins_failed++; + return ret; + } + + if (PIM_DEBUG_TRACE) + zlog_debug("Socket fd=%d left group %pPAs on interface address %pPAs", + fd, &group, &ifaddr); + return ret; +} + #if PIM_IPV == 4 static void cmsg_getdstaddr(struct msghdr *mh, struct sockaddr_storage *dst, ifindex_t *ifindex) diff --git a/pimd/pim_sock.h b/pimd/pim_sock.h index 1cf01b31d6..0a81c6943a 100644 --- a/pimd/pim_sock.h +++ b/pimd/pim_sock.h @@ -32,6 +32,8 @@ int pim_socket_mcast(int protocol, pim_addr ifaddr, struct interface *ifp, uint8_t loop); int pim_socket_join(int fd, pim_addr group, pim_addr ifaddr, ifindex_t ifindex, struct pim_interface *pim_ifp); +int pim_socket_leave(int fd, pim_addr group, pim_addr ifaddr, ifindex_t ifindex, + struct pim_interface *pim_ifp); int pim_socket_recvfromto(int fd, uint8_t *buf, size_t len, struct sockaddr_storage *from, socklen_t *fromlen, struct sockaddr_storage *to, socklen_t *tolen, diff --git a/pimd/pimd.h b/pimd/pimd.h index 3d9318953b..461b7d08a3 100644 --- a/pimd/pimd.h +++ b/pimd/pimd.h @@ -95,6 +95,7 @@ #define PIM_MASK_VXLAN (1 << 26) #define PIM_MASK_BSM_PROC (1 << 27) #define PIM_MASK_MLAG (1 << 28) +#define PIM_MASK_AUTORP (1 << 29) /* Remember 32 bits!!! */ /* PIM error codes */ @@ -167,6 +168,7 @@ extern uint8_t qpim_ecmp_rebalance_enable; #define PIM_DEBUG_MTRACE (router->debugs & PIM_MASK_MTRACE) #define PIM_DEBUG_VXLAN (router->debugs & PIM_MASK_VXLAN) #define PIM_DEBUG_BSM (router->debugs & PIM_MASK_BSM_PROC) +#define PIM_DEBUG_AUTORP (router->debugs & PIM_MASK_AUTORP) #define PIM_DEBUG_EVENTS \ (router->debugs & (PIM_MASK_PIM_EVENTS | PIM_MASK_GM_EVENTS | \ @@ -209,6 +211,7 @@ extern uint8_t qpim_ecmp_rebalance_enable; #define PIM_DO_DEBUG_PIM_NHT_RP (router->debugs |= PIM_MASK_PIM_NHT_RP) #define PIM_DO_DEBUG_MTRACE (router->debugs |= PIM_MASK_MTRACE) #define PIM_DO_DEBUG_VXLAN (router->debugs |= PIM_MASK_VXLAN) +#define PIM_DO_DEBUG_AUTORP (router->debugs |= PIM_MASK_AUTORP) #define PIM_DONT_DEBUG_PIM_EVENTS (router->debugs &= ~PIM_MASK_PIM_EVENTS) #define PIM_DONT_DEBUG_PIM_PACKETS (router->debugs &= ~PIM_MASK_PIM_PACKETS) @@ -243,6 +246,7 @@ extern uint8_t qpim_ecmp_rebalance_enable; #define PIM_DONT_DEBUG_MTRACE (router->debugs &= ~PIM_MASK_MTRACE) #define PIM_DONT_DEBUG_VXLAN (router->debugs &= ~PIM_MASK_VXLAN) #define PIM_DONT_DEBUG_BSM (router->debugs &= ~PIM_MASK_BSM_PROC) +#define PIM_DONT_DEBUG_AUTORP (router->debugs &= ~PIM_MASK_AUTORP) /* RFC 3376: 8.1. Robustness Variable - Default: 2 for IGMP */ /* RFC 2710: 7.1. Robustness Variable - Default: 2 for MLD */ diff --git a/pimd/subdir.am b/pimd/subdir.am index 48f1e3b724..bda594e5c4 100644 --- a/pimd/subdir.am +++ b/pimd/subdir.am @@ -59,6 +59,7 @@ pim_common = \ pimd_pimd_SOURCES = \ $(pim_common) \ + pimd/pim_autorp.c \ pimd/pim_cmd.c \ pimd/pim_igmp.c \ pimd/pim_igmp_mtrace.c \ @@ -98,6 +99,7 @@ nodist_pimd_pim6d_SOURCES = \ noinst_HEADERS += \ pimd/pim_addr.h \ pimd/pim_assert.h \ + pimd/pim_autorp.h \ pimd/pim_bfd.h \ pimd/pim_bsm.h \ pimd/pim_cmd.h \ From 3b323fc441451e109e5f2b2d31b26a8002caf5c4 Mon Sep 17 00:00:00 2001 From: Nathan Bahr Date: Tue, 17 Sep 2024 02:10:03 +0000 Subject: [PATCH 2/6] pimd,yang: Implement AutoRP CLI and NB config path New CLI commands added: router pim [vrf NAME] autorp discovery autorp announce RP-ADDR [GROUP | group-list PREFIX-LIST] autorp announce {scope (1-255) | interval (1-65535) | holdtime (0-65535)} autorp discovery Enables Auto RP discovery for learning dynamic RP information using the AutoRP protocol. autorp announce RP-ADDR [GROUP | group-list PREFIX-LIST] Enable announcements of a candidate RP with the given group range, or prefix list of group ranges, to an AutoRP mapping agent. autorp announce {scope (1-255) | interval (1-65535) | holdtime (0-65535)} Configure the parameters of the AutoRP announcement messages. The scope sets the packet TTL. The interval sets the time between TX of announcements. The holdtime sets the hold time in the message, the time the mapping agent should wait before invalidating the candidate RP information. debug pim autorp Enable debug logging of the AutoRP protocol show ip pim [vrf NAME] autorp [json] Show details of the AutoRP protocol. To view learned RP info, use the existing command 'show ip pim rp-info' Extend pim yang for new configuration: augment /frr-rt:routing/frr-rt:control-plane-protocols/frr-rt:control-plane-protocol/frr-pim:pim/frr-pim:address-family: +--rw rp +--rw auto-rp +--rw discovery-enabled? boolean +--rw announce-scope? uint8 +--rw announce-interval? uint16 +--rw announce-holdtime? uint16 +--rw candidate-rp-list* [rp-address] +--rw rp-address inet:ip-address +--rw (group-or-prefix-list)? +--:(group) | +--rw group? frr-route-types:ip-multicast-group-prefix +--:(prefix-list) +--rw prefix-list? plist-ref Signed-off-by: Nathan Bahr --- pimd/pim_cmd.c | 147 +++++++++++++++++ pimd/pim_cmd.h | 1 + pimd/pim_cmd_common.c | 159 ++++++++++++++++++ pimd/pim_cmd_common.h | 11 +- pimd/pim_nb.c | 49 ++++++ pimd/pim_nb.h | 29 ++++ pimd/pim_nb_config.c | 365 ++++++++++++++++++++++++++++++++++++++++++ pimd/pim_vty.c | 8 + yang/frr-pim-rp.yang | 71 ++++++++ 9 files changed, 839 insertions(+), 1 deletion(-) diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c index 0b503b8293..aa7fc0d81f 100644 --- a/pimd/pim_cmd.c +++ b/pimd/pim_cmd.c @@ -2820,6 +2820,75 @@ DEFPY (show_ip_pim_rp_vrf_all, (struct prefix *)group, !!json); } +DEFPY (show_ip_pim_autorp, + show_ip_pim_autorp_cmd, + "show ip pim [vrf NAME] autorp [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM AutoRP information\n" + JSON_STR) +{ + struct vrf *v; + json_object *json_parent = NULL; + + v = vrf_lookup_by_name(vrf ? vrf : VRF_DEFAULT_NAME); + if (!v || !v->info) { + if (!json) + vty_out(vty, "%% Unable to find pim instance\n"); + return CMD_WARNING; + } + + if (json) + json_parent = json_object_new_object(); + + pim_autorp_show_autorp(vty, v->info, json_parent); + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + +DEFPY (show_ip_pim_autorp_vrf_all, + show_ip_pim_autorp_vrf_all_cmd, + "show ip pim vrf all autorp [json$json]", + SHOW_STR + IP_STR + PIM_STR + VRF_CMD_HELP_STR + "PIM AutoRP information\n" + JSON_STR) +{ + struct vrf *vrf; + json_object *json_parent = NULL; + json_object *json_vrf = NULL; + + if (json) + json_parent = json_object_new_object(); + + RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + if (vrf->info) { + if (!json) + vty_out(vty, "VRF: %s\n", vrf->name); + else + json_vrf = json_object_new_object(); + + pim_autorp_show_autorp(vty, vrf->info, json_vrf); + + if (json) + json_object_object_add(json_parent, vrf->name, + json_vrf); + } + } + + if (json) + vty_json(vty, json_parent); + + return CMD_SUCCESS; +} + DEFPY (show_ip_pim_rpf, show_ip_pim_rpf_cmd, "show ip pim [vrf NAME] rpf [json$json]", @@ -4516,6 +4585,52 @@ DEFPY_ATTR(no_ip_pim_rp_prefix_list, return ret; } +DEFPY (pim_autorp_discovery, + pim_autorp_discovery_cmd, + "[no] autorp discovery", + NO_STR + "AutoRP\n" + "Enable AutoRP discovery\n") +{ + if (no) + return pim_process_no_autorp_cmd(vty); + else + return pim_process_autorp_cmd(vty); +} + +DEFPY (pim_autorp_announce_rp, + pim_autorp_announce_rp_cmd, + "[no] autorp announce A.B.C.D$rpaddr ![A.B.C.D/M$grp|group-list PREFIX_LIST$plist]", + NO_STR + "AutoRP\n" + "AutoRP Candidate RP announcement\n" + "AutoRP Candidate RP address\n" + "Group prefix\n" + "Prefix list\n" + "List name\n") +{ + return pim_process_autorp_candidate_rp_cmd(vty, no, rpaddr_str, grp, + plist); +} + +DEFPY (pim_autorp_announce_scope_int, + pim_autorp_announce_scope_int_cmd, + "[no] autorp announce ![{scope (1-255) | interval (1-65535) | holdtime (0-65535)}]", + NO_STR + "AutoRP\n" + "AutoRP Candidate RP announcement\n" + "Packet scope (TTL)\n" + "TTL value\n" + "Announcement interval\n" + "Time in seconds\n" + "Announcement holdtime\n" + "Time in seconds\n") +{ + return pim_process_autorp_announce_scope_int_cmd(vty, no, scope_str, + interval_str, + holdtime_str); +} + DEFPY (pim_bsr_candidate_bsr, pim_bsr_candidate_bsr_cmd, "[no] bsr candidate-bsr [{priority (0-255)|source
}]", @@ -6377,6 +6492,29 @@ DEFUN (no_debug_bsm, return CMD_SUCCESS; } +DEFUN (debug_autorp, + debug_autorp_cmd, + "debug pim autorp", + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_AUTORP_STR) +{ + PIM_DO_DEBUG_AUTORP; + return CMD_SUCCESS; +} + +DEFUN (no_debug_autorp, + no_debug_autorp_cmd, + "no debug pim autorp", + NO_STR + DEBUG_STR + DEBUG_PIM_STR + DEBUG_PIM_AUTORP_STR) +{ + PIM_DONT_DEBUG_AUTORP; + return CMD_SUCCESS; +} + DEFUN_NOSH (show_debugging_pim, show_debugging_pim_cmd, @@ -8714,6 +8852,9 @@ void pim_cmd_init(void) install_element(PIM_NODE, &no_pim_rp_cmd); install_element(PIM_NODE, &pim_rp_prefix_list_cmd); install_element(PIM_NODE, &no_pim_rp_prefix_list_cmd); + install_element(PIM_NODE, &pim_autorp_discovery_cmd); + install_element(PIM_NODE, &pim_autorp_announce_rp_cmd); + install_element(PIM_NODE, &pim_autorp_announce_scope_int_cmd); install_element(PIM_NODE, &no_pim_ssm_prefix_list_cmd); install_element(PIM_NODE, &no_pim_ssm_prefix_list_name_cmd); install_element(PIM_NODE, &pim_ssm_prefix_list_cmd); @@ -8868,6 +9009,8 @@ void pim_cmd_init(void) install_element(VIEW_NODE, &show_ip_pim_upstream_rpf_cmd); install_element(VIEW_NODE, &show_ip_pim_rp_cmd); install_element(VIEW_NODE, &show_ip_pim_rp_vrf_all_cmd); + install_element(VIEW_NODE, &show_ip_pim_autorp_cmd); + install_element(VIEW_NODE, &show_ip_pim_autorp_vrf_all_cmd); install_element(VIEW_NODE, &show_ip_pim_bsr_cmd); install_element(VIEW_NODE, &show_ip_multicast_cmd); install_element(VIEW_NODE, &show_ip_multicast_vrf_all_cmd); @@ -8975,6 +9118,8 @@ void pim_cmd_init(void) install_element(CONFIG_NODE, &debug_pim_trace_detail_cmd); install_element(ENABLE_NODE, &debug_ssmpingd_cmd); install_element(CONFIG_NODE, &debug_ssmpingd_cmd); + install_element(ENABLE_NODE, &debug_autorp_cmd); + install_element(ENABLE_NODE, &no_debug_autorp_cmd); install_element(ENABLE_NODE, &no_debug_ssmpingd_cmd); install_element(CONFIG_NODE, &no_debug_ssmpingd_cmd); install_element(ENABLE_NODE, &debug_pim_zebra_cmd); @@ -9007,6 +9152,8 @@ void pim_cmd_init(void) install_element(CONFIG_NODE, &debug_bsm_cmd); install_element(ENABLE_NODE, &no_debug_bsm_cmd); install_element(CONFIG_NODE, &no_debug_bsm_cmd); + install_element(CONFIG_NODE, &debug_autorp_cmd); + install_element(CONFIG_NODE, &no_debug_autorp_cmd); install_element(CONFIG_NODE, &ip_igmp_group_watermark_cmd); install_element(VRF_NODE, &ip_igmp_group_watermark_cmd); diff --git a/pimd/pim_cmd.h b/pimd/pim_cmd.h index d39d77cd2f..17cf4bb362 100644 --- a/pimd/pim_cmd.h +++ b/pimd/pim_cmd.h @@ -56,6 +56,7 @@ #define DEBUG_MSDP_PACKETS_STR "MSDP protocol packets\n" #define DEBUG_MTRACE_STR "Mtrace protocol activity\n" #define DEBUG_PIM_BSM_STR "BSR message processing activity\n" +#define DEBUG_PIM_AUTORP_STR "AutoRP message processing activity\n" void pim_cmd_init(void); diff --git a/pimd/pim_cmd_common.c b/pimd/pim_cmd_common.c index 7337bacc84..be7460d0fb 100644 --- a/pimd/pim_cmd_common.c +++ b/pimd/pim_cmd_common.c @@ -606,6 +606,165 @@ int pim_process_no_rp_plist_cmd(struct vty *vty, const char *rp_str, return nb_cli_apply_changes(vty, NULL); } +int pim_process_autorp_cmd(struct vty *vty) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "%s/%s", FRR_PIM_AUTORP_XPATH, + "discovery-enabled"); + + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, "true"); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_no_autorp_cmd(struct vty *vty) +{ + char xpath[XPATH_MAXLEN]; + + snprintf(xpath, sizeof(xpath), "%s/%s", FRR_PIM_AUTORP_XPATH, + "discovery-enabled"); + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_autorp_candidate_rp_cmd(struct vty *vty, bool no, + const char *rpaddr_str, + const struct prefix_ipv4 *grp, + const char *plist) +{ + char xpath[XPATH_MAXLEN]; + char grpstr[64]; + + if (no) { + if (!is_default_prefix((const struct prefix *)grp) || plist) { + /* If any single values are set, only destroy those */ + if (!is_default_prefix((const struct prefix *)grp)) { + snprintfrr(xpath, sizeof(xpath), + "%s/candidate-rp-list[rp-address='%s']/group", + FRR_PIM_AUTORP_XPATH, rpaddr_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, + NULL); + } + if (plist) { + snprintfrr(xpath, sizeof(xpath), + "%s/candidate-rp-list[rp-address='%s']/prefix-list", + FRR_PIM_AUTORP_XPATH, rpaddr_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, + NULL); + } + } else { + /* No values set, remove the entire RP */ + snprintfrr(xpath, sizeof(xpath), + "%s/candidate-rp-list[rp-address='%s']", + FRR_PIM_AUTORP_XPATH, rpaddr_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + } + } else { + if (!is_default_prefix((const struct prefix *)grp) || plist) { + snprintfrr(xpath, sizeof(xpath), + "%s/candidate-rp-list[rp-address='%s']", + FRR_PIM_AUTORP_XPATH, rpaddr_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + if (!is_default_prefix((const struct prefix *)grp)) { + snprintfrr(xpath, sizeof(xpath), + "%s/candidate-rp-list[rp-address='%s']/group", + FRR_PIM_AUTORP_XPATH, rpaddr_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + prefix2str(grp, grpstr, + sizeof(grpstr))); + } + if (plist) { + snprintfrr(xpath, sizeof(xpath), + "%s/candidate-rp-list[rp-address='%s']/prefix-list", + FRR_PIM_AUTORP_XPATH, rpaddr_str); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + plist); + } + } else { + return CMD_WARNING_CONFIG_FAILED; + } + } + + return nb_cli_apply_changes(vty, NULL); +} + +int pim_process_autorp_announce_scope_int_cmd(struct vty *vty, bool no, + const char *scope, + const char *interval, + const char *holdtime) +{ + char xpath[XPATH_MAXLEN]; + + if (no) { + if (scope || interval || holdtime) { + /* If any single values are set, only destroy those */ + if (scope) { + snprintfrr(xpath, sizeof(xpath), "%s/%s", + FRR_PIM_AUTORP_XPATH, + "announce-scope"); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, + NULL); + } + if (interval) { + snprintfrr(xpath, sizeof(xpath), "%s/%s", + FRR_PIM_AUTORP_XPATH, + "announce-interval"); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, + NULL); + } + if (holdtime) { + snprintfrr(xpath, sizeof(xpath), "%s/%s", + FRR_PIM_AUTORP_XPATH, + "announce-holdtime"); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, + NULL); + } + } else { + /* No values set, remove all */ + snprintfrr(xpath, sizeof(xpath), "%s/%s", + FRR_PIM_AUTORP_XPATH, "announce-scope"); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + snprintfrr(xpath, sizeof(xpath), "%s/%s", + FRR_PIM_AUTORP_XPATH, "announce-interval"); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + snprintfrr(xpath, sizeof(xpath), "%s/%s", + FRR_PIM_AUTORP_XPATH, "announce-holdtime"); + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + } + } else { + if (scope || interval || holdtime) { + if (scope) { + snprintfrr(xpath, sizeof(xpath), "%s/%s", + FRR_PIM_AUTORP_XPATH, + "announce-scope"); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + scope); + } + if (interval) { + snprintfrr(xpath, sizeof(xpath), "%s/%s", + FRR_PIM_AUTORP_XPATH, + "announce-interval"); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + interval); + } + if (holdtime) { + snprintfrr(xpath, sizeof(xpath), "%s/%s", + FRR_PIM_AUTORP_XPATH, + "announce-holdtime"); + nb_cli_enqueue_change(vty, xpath, NB_OP_MODIFY, + holdtime); + } + } else { + return CMD_WARNING_CONFIG_FAILED; + } + } + + return nb_cli_apply_changes(vty, NULL); +} + bool pim_sgaddr_match(pim_sgaddr item, pim_sgaddr match) { return (pim_addr_is_any(match.grp) || diff --git a/pimd/pim_cmd_common.h b/pimd/pim_cmd_common.h index 7ded9b246a..d7c97e31d4 100644 --- a/pimd/pim_cmd_common.h +++ b/pimd/pim_cmd_common.h @@ -35,7 +35,16 @@ int pim_process_rp_plist_cmd(struct vty *vty, const char *rp_str, const char *prefix_list); int pim_process_no_rp_plist_cmd(struct vty *vty, const char *rp_str, const char *prefix_list); - +int pim_process_autorp_cmd(struct vty *vty); +int pim_process_no_autorp_cmd(struct vty *vty); +int pim_process_autorp_candidate_rp_cmd(struct vty *vty, bool no, + const char *rpaddr_str, + const struct prefix_ipv4 *grp, + const char *plist); +int pim_process_autorp_announce_scope_int_cmd(struct vty *vty, bool no, + const char *scope, + const char *interval, + const char *holdtime); int pim_process_ip_pim_cmd(struct vty *vty); int pim_process_no_ip_pim_cmd(struct vty *vty); int pim_process_ip_pim_passive_cmd(struct vty *vty, bool enable); diff --git a/pimd/pim_nb.c b/pimd/pim_nb.c index 3c36512a3d..66001d1463 100644 --- a/pimd/pim_nb.c +++ b/pimd/pim_nb.c @@ -379,6 +379,55 @@ const struct frr_yang_module_info frr_pim_rp_info = { .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_prefix_list_destroy, } }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/discovery-enabled", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_discovery_enabled_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_discovery_enabled_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/announce-scope", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_scope_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_scope_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/announce-interval", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_interval_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_interval_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/announce-holdtime", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_holdtime_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_holdtime_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/candidate-rp-list", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/candidate-rp-list/group", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_group_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_group_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/candidate-rp-list/prefix-list", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_prefix_list_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_prefix_list_destroy, + } + }, { .xpath = NULL, }, diff --git a/pimd/pim_nb.h b/pimd/pim_nb.h index dfab582968..befad4efe4 100644 --- a/pimd/pim_nb.h +++ b/pimd/pim_nb.h @@ -159,6 +159,34 @@ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp struct nb_cb_modify_args *args); int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_static_rp_rp_list_prefix_list_destroy( struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_discovery_enabled_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_discovery_enabled_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_scope_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_scope_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_interval_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_interval_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_holdtime_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_holdtime_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_group_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_group_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_prefix_list_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_prefix_list_destroy( + struct nb_cb_destroy_args *args); /* frr-cand-bsr */ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_candidate_bsr_create( @@ -258,6 +286,7 @@ int routing_control_plane_protocols_name_validate( "mroute[source-addr='%s'][group-addr='%s']" #define FRR_PIM_STATIC_RP_XPATH \ "frr-pim-rp:rp/static-rp/rp-list[rp-address='%s']" +#define FRR_PIM_AUTORP_XPATH "./frr-pim-rp:rp/auto-rp" #define FRR_GMP_INTERFACE_XPATH \ "./frr-gmp:gmp/address-family[address-family='%s']" #define FRR_GMP_ENABLE_XPATH \ diff --git a/pimd/pim_nb_config.c b/pimd/pim_nb_config.c index 0366d8a857..2b78b8671f 100644 --- a/pimd/pim_nb_config.c +++ b/pimd/pim_nb_config.c @@ -26,6 +26,7 @@ #include "lib_errors.h" #include "pim_util.h" #include "pim6_mld.h" +#include "pim_autorp.h" #include "pim_igmp.h" #if PIM_IPV == 6 @@ -147,6 +148,11 @@ static int pim_cmd_interface_add(struct interface *ifp) pim_if_membership_refresh(ifp); pim_if_create_pimreg(pim_ifp->pim); + +#if PIM_IPV == 4 + pim_autorp_add_ifp(ifp); +#endif + return 1; } @@ -2680,6 +2686,365 @@ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp return NB_OK; } +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/discovery-enabled + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_discovery_enabled_modify( + struct nb_cb_modify_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + bool enabled; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + enabled = yang_dnode_get_bool(args->dnode, NULL); + if (enabled) + pim_autorp_start_discovery(pim); + else + pim_autorp_stop_discovery(pim); + break; + } +#endif + + return NB_OK; +} +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_discovery_enabled_destroy( + struct nb_cb_destroy_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + bool enabled; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + enabled = yang_dnode_get_bool(args->dnode, NULL); + /* Run AutoRP discovery by default */ + if (!enabled) + pim_autorp_start_discovery(pim); + break; + } +#endif + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/announce-scope + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_scope_modify( + struct nb_cb_modify_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + uint8_t scope; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + scope = yang_dnode_get_uint8(args->dnode, NULL); + pim_autorp_announce_scope(pim, scope); + } +#endif + + return NB_OK; +} +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_scope_destroy( + struct nb_cb_destroy_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + pim_autorp_announce_scope(pim, 0); + } +#endif + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/announce-interval + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_interval_modify( + struct nb_cb_modify_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + uint16_t interval; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + interval = yang_dnode_get_uint16(args->dnode, NULL); + pim_autorp_announce_interval(pim, interval); + } +#endif + + return NB_OK; +} +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_interval_destroy( + struct nb_cb_destroy_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + pim_autorp_announce_interval(pim, 0); + } +#endif + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/announce-holdtime + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_holdtime_modify( + struct nb_cb_modify_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + uint16_t holdtime; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + holdtime = yang_dnode_get_uint16(args->dnode, NULL); + pim_autorp_announce_holdtime(pim, holdtime); + } +#endif + + return NB_OK; +} +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_announce_holdtime_destroy( + struct nb_cb_destroy_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + /* 0 is a valid value, so -1 indicates deleting (go back to default) */ + pim_autorp_announce_holdtime(pim, -1); + } +#endif + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/candidate-rp-list + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_create( + struct nb_cb_create_args *args) +{ +#if PIM_IPV == 4 + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + case NB_EV_APPLY: + break; + } +#endif + + return NB_OK; +} +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_destroy( + struct nb_cb_destroy_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + pim_addr rp_addr; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + yang_dnode_get_pimaddr(&rp_addr, args->dnode, "rp-address"); + if (!pim_autorp_rm_candidate_rp(pim, rp_addr)) + return NB_ERR_INCONSISTENCY; + break; + } +#endif + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/candidate-rp-list/group + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_group_modify( + struct nb_cb_modify_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + struct prefix group; + pim_addr rp_addr; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + yang_dnode_get_pimaddr(&rp_addr, args->dnode, "../rp-address"); + yang_dnode_get_prefix(&group, args->dnode, NULL); + apply_mask(&group); + pim_autorp_add_candidate_rp_group(pim, rp_addr, group); + } +#endif + + return NB_OK; +} +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_group_destroy( + struct nb_cb_destroy_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + struct prefix group; + pim_addr rp_addr; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + yang_dnode_get_pimaddr(&rp_addr, args->dnode, "../rp-address"); + yang_dnode_get_prefix(&group, args->dnode, NULL); + apply_mask(&group); + if (!pim_autorp_rm_candidate_rp_group(pim, rp_addr, group)) + return NB_ERR_INCONSISTENCY; + } +#endif + + return NB_OK; +} + +/* + * XPath: /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-pim:pim/address-family/frr-pim-rp:rp/auto-rp/candidate-rp-list/prefix-list + */ +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_prefix_list_modify( + struct nb_cb_modify_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + pim_addr rp_addr; + const char *plist; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + plist = yang_dnode_get_string(args->dnode, NULL); + yang_dnode_get_pimaddr(&rp_addr, args->dnode, "../rp-address"); + pim_autorp_add_candidate_rp_plist(pim, rp_addr, plist); + } +#endif + + return NB_OK; +} +int routing_control_plane_protocols_control_plane_protocol_pim_address_family_rp_auto_rp_candidate_rp_list_prefix_list_destroy( + struct nb_cb_destroy_args *args) +{ +#if PIM_IPV == 4 + struct vrf *vrf; + struct pim_instance *pim; + pim_addr rp_addr; + const char *plist; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + vrf = nb_running_get_entry(args->dnode, NULL, true); + pim = vrf->info; + yang_dnode_get_pimaddr(&rp_addr, args->dnode, "../rp-address"); + plist = yang_dnode_get_string(args->dnode, NULL); + if (!pim_autorp_rm_candidate_rp_plist(pim, rp_addr, plist)) + return NB_ERR_INCONSISTENCY; + break; + } +#endif + + return NB_OK; +} + static void yang_addrsel(struct cand_addrsel *addrsel, const struct lyd_node *node) { diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c index d5d6fb54d5..b633e81d55 100644 --- a/pimd/pim_vty.c +++ b/pimd/pim_vty.c @@ -165,6 +165,11 @@ int pim_debug_config_write(struct vty *vty) ++writes; } + if (PIM_DEBUG_AUTORP) { + vty_out(vty, "debug pim autorp\n"); + ++writes; + } + return writes; } @@ -182,6 +187,9 @@ int pim_global_config_write_worker(struct pim_instance *pim, struct vty *vty) } writes += pim_rp_config_write(pim, vty); +#if PIM_IPV == 4 + writes += pim_autorp_config_write(pim, vty); +#endif writes += pim_cand_config_write(pim, vty); if (pim->vrf->vrf_id == VRF_DEFAULT) { diff --git a/yang/frr-pim-rp.yang b/yang/frr-pim-rp.yang index 4cc214135d..dbd5513ee5 100644 --- a/yang/frr-pim-rp.yang +++ b/yang/frr-pim-rp.yang @@ -111,6 +111,70 @@ module frr-pim-rp { } // static-rp } // static-rp-container + grouping auto-rp-container { + description + "Grouping of AutoRP container."; + + container auto-rp { + description + "Containing AutoRP attributes."; + + leaf discovery-enabled { + type boolean; + description + "Flag indicating if Auto RP discovery is enabled."; + } + + leaf announce-scope { + type uint8; + description + "The TTL of the C-RP Announcement packet."; + } + + leaf announce-interval { + type uint16; + description + "The time between sending C-RP announcement packets."; + } + + leaf announce-holdtime { + type uint16; + description + "The hold time in seconds advertised in the announcement packet."; + } + + list candidate-rp-list { + key "rp-address"; + description + "A list of Candidate RP addresses."; + + leaf rp-address { + type inet:ip-address; + description + "Specifies a candidate RP address."; + } + + choice group-or-prefix-list { + description "Use group or prefix-list"; + case group { + leaf group { + type frr-route-types:ip-multicast-group-prefix; + description + "Multicast group prefix."; + } + } + case prefix-list { + leaf prefix-list { + type plist-ref; + description + "Group prefix-list filter"; + } + } + } + } // candidate-rp-list + } // auto-rp + } // auto-rp-container + /* * Configuration data nodes */ @@ -123,6 +187,13 @@ module frr-pim-rp { description "PIM RP configuration data."; uses static-rp-container; + + uses auto-rp-container { + when "../frr-pim:address-family = 'frr-rt:ipv4'" { + description + "Only applicable to IPv4 address family."; + } + } } // rp } // augment } From cbe39620f9f8037068fc82f2f81b134d3694311a Mon Sep 17 00:00:00 2001 From: Nathan Bahr Date: Tue, 17 Sep 2024 02:35:27 +0000 Subject: [PATCH 3/6] doc: Add user documentation for AutoRP CLI Document the CLI commands to configure AutoRP discovery and candidate RP announcements. Signed-off-by: Nathan Bahr --- doc/user/pim.rst | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/user/pim.rst b/doc/user/pim.rst index d6d9ae04d2..0fe53247b0 100644 --- a/doc/user/pim.rst +++ b/doc/user/pim.rst @@ -71,6 +71,31 @@ PIM Routers prefix of group ranges covered. This command is vrf aware, to configure for a vrf, specify the vrf in the router pim block. +.. clicmd:: no autorp discovery + + In order to use pim, it is necessary to configure a RP for join messages to + be sent to. FRR supports learning RP information dynamically via the AutoRP + protocol and performs discovery by default. This command will disable the + AutoRP discovery protocol. + All routers in the pim network must agree on the network RP information, so + all routers in the network should have AutoRP either enabled or disabled. + This command is vrf aware, to configure for a vrf, specify the vrf in the + router pim block. + +.. clicmd:: autorp announce A.B.C.D [A.B.C.D/M | group-list PREFIX_LIST] + + Configure the router to advertise itself as a candidate PIM-SM RP via AutoRP. + The supported groups can be defined as a single group range, or multiple + group ranges can be defined via a prefix list. + +.. clicmd:: autorp announce {scope (1-255) | interval (1-65535) | holdtime (0-65535)} + + Configure the AutoRP advertise messages. The scope defines the TTL value in the + messages to limit the scope, defaults to 31. Interval defines the number of + seconds elapsed between advertise messages sent, defaults to 60. Hold time defines + how long the AutoRP mapping agent will consider the information valid, setting to + 0 will disable expiration of the candidate RP information, defaults to 3 * interval. + .. clicmd:: rp keep-alive-timer (1-65535) Modify the time out value for a S,G flow from 1-65535 seconds at RP. @@ -616,6 +641,11 @@ cause great confusion. 192.168.10.123 239.0.0.0/8 eth2 yes Static ASM 192.168.10.123 239.4.0.0/24 eth2 yes Static SSM +.. clicmd:: show ip pim [vrf NAME] autorp [json] + + Display information about AutoRP. Including state of AutoRP Discovery parsing + and configured AutoRP candidate RP information. + .. clicmd:: show ip pim rpf Display information about currently being used S,G's and their RPF lookup @@ -761,6 +791,10 @@ the config was written out. This gathers data about events from zebra that come up through the ZAPI. +.. clicmd:: debug pim autorp + + This turns on debugging for PIM AutoRP protocol events. + PIM Clear Commands ================== Clear commands reset various variables. From af45a16fce9e393a6091808d082bd943e077b142 Mon Sep 17 00:00:00 2001 From: Nathan Bahr Date: Tue, 17 Sep 2024 21:23:19 +0000 Subject: [PATCH 4/6] tests: Added tests for new AutoRP functionality Uses hardcoded sample AutoRP packets injected in to test message parsing and proper application of AutoRP learned RP info. Tests mix of AutoRP and static RP's. Signed-off-by: Nathan Bahr --- tests/topotests/lib/pim.py | 85 +++++++ tests/topotests/pim_autorp/__init__.py | 0 tests/topotests/pim_autorp/r1/frr.conf | 16 ++ tests/topotests/pim_autorp/r2/frr.conf | 16 ++ tests/topotests/pim_autorp/test_pim_autorp.py | 207 ++++++++++++++++++ 5 files changed, 324 insertions(+) create mode 100755 tests/topotests/pim_autorp/__init__.py create mode 100644 tests/topotests/pim_autorp/r1/frr.conf create mode 100644 tests/topotests/pim_autorp/r2/frr.conf create mode 100644 tests/topotests/pim_autorp/test_pim_autorp.py diff --git a/tests/topotests/lib/pim.py b/tests/topotests/lib/pim.py index 2062f65561..9eb3a076a7 100644 --- a/tests/topotests/lib/pim.py +++ b/tests/topotests/lib/pim.py @@ -1745,6 +1745,49 @@ def verify_pim_rp_info( return True +@retry(retry_timeout=60, diag_pct=0) +def verify_pim_rp_info_is_empty(tgen, dut, af="ipv4"): + """ + Verify pim rp info by running "show ip pim rp-info" cli + + Parameters + ---------- + * `tgen`: topogen object + * `dut`: device under test + + Usage + ----- + dut = "r1" + result = verify_pim_rp_info_is_empty(tgen, dut) + + Returns + ------- + errormsg(str) or True + """ + + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + if dut not in tgen.routers(): + return False + + rnode = tgen.routers()[dut] + + ip_cmd = "ip" + if af == "ipv6": + ip_cmd = "ipv6" + + logger.info("[DUT: %s]: Verifying %s rp info", dut, ip_cmd) + cmd = "show {} pim rp-info json".format(ip_cmd) + show_ip_rp_info_json = run_frr_cmd(rnode, cmd, isjson=True) + + if show_ip_rp_info_json: + errormsg = "[DUT %s]: Verifying empty rp-info [FAILED]!!" % (dut) + return errormsg + + logger.debug("Exiting lib API: {}".format(sys._getframe().f_code.co_name)) + return True + + @retry(retry_timeout=60, diag_pct=0) def verify_pim_state( tgen, @@ -2751,6 +2794,48 @@ def scapy_send_bsr_raw_packet(tgen, topo, senderRouter, receiverRouter, packet=N return True +def scapy_send_autorp_raw_packet(tgen, senderRouter, senderInterface, packet=None): + """ + Using scapy Raw() method to send AutoRP raw packet from one FRR + to other + + Parameters: + ----------- + * `tgen` : Topogen object + * `senderRouter` : Sender router + * `senderInterface` : SenderInterface + * `packet` : AutoRP packet in raw format + + returns: + -------- + errormsg or True + """ + + global CWD + result = "" + logger.debug("Entering lib API: {}".format(sys._getframe().f_code.co_name)) + + python3_path = tgen.net.get_exec_path(["python3", "python"]) + # send_bsr_packet.py has no direct ties to bsr, just sends a raw packet out + # a given interface, so just reuse it + script_path = os.path.join(CWD, "send_bsr_packet.py") + node = tgen.net[senderRouter] + + cmd = [ + python3_path, + script_path, + packet, + senderInterface, + "--interval=1", + "--count=1", + ] + logger.info("Scapy cmd: \n %s", cmd) + node.cmd_raises(cmd) + + logger.debug("Exiting lib API: scapy_send_autorp_raw_packet") + return True + + def find_rp_from_bsrp_info(tgen, dut, bsr, grp=None): """ Find which RP is having lowest prioriy and returns rp IP diff --git a/tests/topotests/pim_autorp/__init__.py b/tests/topotests/pim_autorp/__init__.py new file mode 100755 index 0000000000..e69de29bb2 diff --git a/tests/topotests/pim_autorp/r1/frr.conf b/tests/topotests/pim_autorp/r1/frr.conf new file mode 100644 index 0000000000..2fddbc3ae2 --- /dev/null +++ b/tests/topotests/pim_autorp/r1/frr.conf @@ -0,0 +1,16 @@ +! +hostname r1 +password zebra +log file /tmp/r1-frr.log +debug pim autorp +! +interface r1-eth0 + ip address 10.10.76.1/24 + ip igmp + ip pim +! +ip forwarding +! +router pim + autorp discovery +! \ No newline at end of file diff --git a/tests/topotests/pim_autorp/r2/frr.conf b/tests/topotests/pim_autorp/r2/frr.conf new file mode 100644 index 0000000000..fd3c0cad39 --- /dev/null +++ b/tests/topotests/pim_autorp/r2/frr.conf @@ -0,0 +1,16 @@ +! +hostname r2 +password zebra +log file /tmp/r2-frr.log +debug pim autorp +! +interface r2-eth0 + ip address 10.10.76.2/24 + ip igmp + ip pim +! +ip forwarding +! +router pim + autorp discovery +! \ No newline at end of file diff --git a/tests/topotests/pim_autorp/test_pim_autorp.py b/tests/topotests/pim_autorp/test_pim_autorp.py new file mode 100644 index 0000000000..5aecce942e --- /dev/null +++ b/tests/topotests/pim_autorp/test_pim_autorp.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# test_pim_autorp.py +# +# Copyright (c) 2024 ATCorp +# Nathan Bahr +# + +import os +import sys +import pytest + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from lib.topolog import logger +from lib.pim import scapy_send_autorp_raw_packet, verify_pim_rp_info, verify_pim_rp_info_is_empty +from lib.common_config import step, write_test_header + +from time import sleep + +""" +test_pim_autorp.py: Test general PIM AutoRP functionality +""" + +TOPOLOGY = """ + Basic AutoRP functionality + + +---+---+ +---+---+ + | | 10.10.76.0/24 | | + + R1 + <------------------> + R2 | + | | .1 .2 | | + +---+---+ +---+---+ +""" + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# Required to instantiate the topology builder class. +pytestmark = [pytest.mark.pimd] + + +def build_topo(tgen): + "Build function" + + # Create routers + tgen.add_router("r1") + tgen.add_router("r2") + + # Create link between router 1 and 2 + switch = tgen.add_switch("s1-2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + +def setup_module(mod): + logger.info("PIM AutoRP basic functionality:\n {}".format(TOPOLOGY)) + + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + # Router 1 will be the router configured with "fake" autorp configuration, so give it a default route + # to router 2 so that routing to the RP address is not an issue + # r1_defrt_setup_cmds = [ + # "ip route add default via 10.10.76.1 dev r1-eth0", + # ] + # for cmd in r1_defrt_setup_cmds: + # tgen.net["r1"].cmd(cmd) + + logger.info("Testing PIM AutoRP support") + router_list = tgen.routers() + for rname, router in router_list.items(): + logger.info("Loading router %s" % rname) + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + # Initialize all routers. + tgen.start_router() + for router in router_list.values(): + if router.has_version("<", "4.0"): + tgen.set_error("unsupported version") + + +def teardown_module(mod): + "Teardown the pytest environment" + tgen = get_topogen() + tgen.stop_topology() + +def test_pim_autorp_discovery_single_rp(request): + "Test PIM AutoRP Discovery with single RP" + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Start with no RP configuration") + result = verify_pim_rp_info_is_empty(tgen, "r1") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Send AutoRP packet from r1 to r2") + # 1 RP(s), hold time 5 secs, 10.10.76.1, group(s) 224.0.0.0/4 + data = "01005e00012800127f55cfb1080045c00030700c000008110abe0a0a4c01e000012801f001f0001c798b12010005000000000a0a4c0103010004e0000000" + scapy_send_autorp_raw_packet(tgen, "r1", "r1-eth0", data) + + step("Verify rp-info from AutoRP packet") + result = verify_pim_rp_info(tgen, None, "r2", "224.0.0.0/4", "r2-eth0", "10.10.76.1", "AutoRP", False, "ipv4", True) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Verify AutoRP configuration times out") + result = verify_pim_rp_info_is_empty(tgen, "r2") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + +def test_pim_autorp_discovery_multiple_rp(request): + "Test PIM AutoRP Discovery with multiple RP's" + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + step("Start with no RP configuration") + result = verify_pim_rp_info_is_empty(tgen, "r2") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Send AutoRP packet from r1 to r2") + # 2 RP(s), hold time 5 secs, 10.10.76.1, group(s) 224.0.0.0/8, 10.10.76.3, group(s) 225.0.0.0/8 + data = "01005e00012800127f55cfb1080045c0003c700c000008110ab20a0a4c01e000012801f001f000283f5712020005000000000a0a4c0103010008e00000000a0a4c0303010008e1000000" + scapy_send_autorp_raw_packet(tgen, "r1", "r1-eth0", data) + + step("Verify rp-info from AutoRP packet") + result = verify_pim_rp_info(tgen, None, "r2", "224.0.0.0/8", "r2-eth0", "10.10.76.1", "AutoRP", False, "ipv4", True) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + result = verify_pim_rp_info(tgen, None, "r2", "225.0.0.0/8", "r2-eth0", "10.10.76.3", "AutoRP", False, "ipv4", True) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + +def test_pim_autorp_discovery_static(request): + "Test PIM AutoRP Discovery with Static RP" + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + step("Start with no RP configuration") + result = verify_pim_rp_info_is_empty(tgen, "r2") + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Add static RP configuration to r2") + rnode = tgen.routers()["r2"] + rnode.cmd("vtysh -c 'conf t' -c 'router pim' -c 'rp 10.10.76.3 224.0.0.0/4'") + + step("Verify static rp-info from r2") + result = verify_pim_rp_info(tgen, None, "r2", "224.0.0.0/4", "r2-eth0", "10.10.76.3", "Static", False, "ipv4", True) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + step("Send AutoRP packet from r1 to r2") + # 1 RP(s), hold time 5 secs, 10.10.76.1, group(s) 224.0.0.0/4 + data = "01005e00012800127f55cfb1080045c00030700c000008110abe0a0a4c01e000012801f001f0001c798b12010005000000000a0a4c0103010004e0000000" + scapy_send_autorp_raw_packet(tgen, "r1", "r1-eth0", data) + + step("Verify rp-info from AutoRP packet") + result = verify_pim_rp_info(tgen, None, "r2", "224.0.0.0/4", "r2-eth0", "10.10.76.1", "AutoRP", False, "ipv4", True) + assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + + +def test_pim_autorp_announce_group(request): + "Test PIM AutoRP Announcement with a single group" + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + pytest.skip("skipped because of router(s) failure") + + step("Add candidate RP configuration to r1") + rnode = tgen.routers()["r1"] + rnode.cmd("vtysh -c 'conf t' -c 'router pim' -c 'send-rp-announce 10.10.76.1 224.0.0.0/4'") + step("Verify Announcement sent data") + # TODO: Verify AutoRP mapping agent receives candidate RP announcement + # Mapping agent is not yet implemented + #sleep(10) + step("Change AutoRP Announcement packet parameters") + rnode.cmd("vtysh -c 'conf t' -c 'router pim' -c 'send-rp-announce scope 8 interval 10 holdtime 60'") + step("Verify Announcement sent data") + # TODO: Verify AutoRP mapping agent receives updated candidate RP announcement + # Mapping agent is not yet implemented + #sleep(10) + + +def test_memory_leak(): + "Run the memory leak test and report results." + tgen = get_topogen() + if not tgen.is_memleak_enabled(): + pytest.skip("Memory leak test/report is disabled") + + tgen.report_memory_leaks() + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) From ec37f603a1e1a86904e10f37d4f2e23b0d71999d Mon Sep 17 00:00:00 2001 From: Nathan Bahr Date: Wed, 18 Sep 2024 14:58:48 +0000 Subject: [PATCH 5/6] tests: Update existing tests for changes introduced by AutoRP Discovery With AutoRP discovery running by default, that adds a new IGMP group that needs to be accounted for in IGMP output. For multicast_pim_sm_topo3: Ignore the total group number as it is unnecessary for the test. Signed-off-by: Nathan Bahr --- .../topotests/multicast_pim_sm_topo3/igmp_group_all_detail.json | 2 +- .../multicast_pim_sm_topo3/igmp_single_if_group_all_brief.json | 2 -- .../multicast_pim_sm_topo3/igmp_single_if_group_all_detail.json | 2 +- .../igmp_single_if_single_group_brief.json | 1 - .../igmp_single_if_single_group_detail.json | 2 +- 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_group_all_detail.json b/tests/topotests/multicast_pim_sm_topo3/igmp_group_all_detail.json index 715aa1de72..44a7db56f1 100644 --- a/tests/topotests/multicast_pim_sm_topo3/igmp_group_all_detail.json +++ b/tests/topotests/multicast_pim_sm_topo3/igmp_group_all_detail.json @@ -1 +1 @@ -{"totalGroups":5,"watermarkLimit":0,"l1-i1-eth1":{"name":"l1-i1-eth1","state":"up","address":"10.0.8.2","index":"*","flagMulticast":true,"flagBroadcast":true,"lanDelayEnabled":true,"groups":[{"group":"225.1.1.2","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.1","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.3","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.5","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.4","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]}]}} +{"watermarkLimit":0,"l1-i1-eth1":{"name":"l1-i1-eth1","state":"up","address":"10.0.8.2","index":"*","flagMulticast":true,"flagBroadcast":true,"lanDelayEnabled":true,"groups":[{"group":"225.1.1.2","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.1","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.3","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.5","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.4","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]}]}} diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_brief.json b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_brief.json index 3bbcce1370..982157a624 100644 --- a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_brief.json +++ b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_brief.json @@ -1,5 +1,4 @@ { - "totalGroups":5, "watermarkLimit":0, "l1-i1-eth1":{ "name":"l1-i1-eth1", @@ -48,4 +47,3 @@ ] } } - diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_detail.json b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_detail.json index 876befa1b8..6042ef4dbf 100644 --- a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_detail.json +++ b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_group_all_detail.json @@ -1 +1 @@ -{"totalGroups":5,"watermarkLimit":0,"l1-i1-eth1":{"name":"l1-i1-eth1","state":"up","address":"10.0.8.2","index":"*","flagMulticast":true,"flagBroadcast":true,"lanDelayEnabled":true,"groups":[{"group":"225.1.1.1","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.2","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.3","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.4","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.5","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]}]}} +{"watermarkLimit":0,"l1-i1-eth1":{"name":"l1-i1-eth1","state":"up","address":"10.0.8.2","index":"*","flagMulticast":true,"flagBroadcast":true,"lanDelayEnabled":true,"groups":[{"group":"225.1.1.1","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.2","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.3","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.4","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]},{"group":"225.1.1.5","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]}]}} diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_brief.json b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_brief.json index a3fb496d25..0312c3026d 100644 --- a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_brief.json +++ b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_brief.json @@ -1,5 +1,4 @@ { - "totalGroups":5, "watermarkLimit":0, "l1-i1-eth1":{ "name":"l1-i1-eth1", diff --git a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_detail.json b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_detail.json index 11ac5a01e7..537be377be 100644 --- a/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_detail.json +++ b/tests/topotests/multicast_pim_sm_topo3/igmp_single_if_single_group_detail.json @@ -1 +1 @@ -{"totalGroups":5,"watermarkLimit":0,"l1-i1-eth1":{"name":"l1-i1-eth1","state":"up","address":"10.0.8.2","index":"*","flagMulticast":true,"flagBroadcast":true,"lanDelayEnabled":true,"groups":[{"group":"225.1.1.5","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]}]}} +{"watermarkLimit":0,"l1-i1-eth1":{"name":"l1-i1-eth1","state":"up","address":"10.0.8.2","index":"*","flagMulticast":true,"flagBroadcast":true,"lanDelayEnabled":true,"groups":[{"group":"225.1.1.5","timer":"*","sourcesCount":1,"version":2,"uptime":"*","sources":[{"source":"*","timer":"*","forwarded":true,"uptime":"*"}]}]}} From dff1db9ef699a768806d1aa752f0166d33339bc9 Mon Sep 17 00:00:00 2001 From: Nathan Bahr Date: Wed, 18 Sep 2024 15:00:10 +0000 Subject: [PATCH 6/6] tests: Addition of AutoRP Discovery uncovered broken PIM test With AutoRP discovery running by default, that adds a new IGMP group that needs to be accounted for in IGMP output. For pim.py The clear IGMP interfaces function is in a broken state. It was already ignoring any errors and returned true always, but with the addition of the AutoRP discovery group, you could end up with a different group order in the json which would cause a key error making the test fail. For now I just added a check to avoid the key error. Signed-off-by: Nathan Bahr --- tests/topotests/lib/pim.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/topotests/lib/pim.py b/tests/topotests/lib/pim.py index 9eb3a076a7..369a794ebc 100644 --- a/tests/topotests/lib/pim.py +++ b/tests/topotests/lib/pim.py @@ -2454,10 +2454,11 @@ def clear_igmp_interfaces(tgen, dut): # Verify uptime for groups for group in group_before_clear.keys(): - d1 = datetime.datetime.strptime(group_before_clear[group], "%H:%M:%S") - d2 = datetime.datetime.strptime(group_after_clear[group], "%H:%M:%S") - if d2 >= d1: - errormsg = ("[DUT: %s]: IGMP group is not cleared", " [FAILED!!]", dut) + if group in group_after_clear: + d1 = datetime.datetime.strptime(group_before_clear[group], "%H:%M:%S") + d2 = datetime.datetime.strptime(group_after_clear[group], "%H:%M:%S") + if d2 >= d1: + errormsg = ("[DUT: %s]: IGMP group is not cleared", " [FAILED!!]", dut) logger.info("[DUT: %s]: IGMP group is cleared [PASSED!!]")