// 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_util.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 discovered RP info"); DEFINE_MTYPE_STATIC(PIMD, PIM_AUTORP_ANNOUNCE, "PIM AutoRP announcement packet"); DEFINE_MTYPE_STATIC(PIMD, PIM_AUTORP_GRPPFIX, "PIM AutoRP group prefix list"); 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, item, pim_autorp_rp_cmp); static int pim_autorp_grppfix_cmp(const struct pim_autorp_grppfix *l, const struct pim_autorp_grppfix *r) { return prefix_cmp(&l->grp, &r->grp); } DECLARE_SORTLIST_UNIQ(pim_autorp_grppfix, struct pim_autorp_grppfix, item, pim_autorp_grppfix_cmp); static void pim_autorp_grppfix_free(struct pim_autorp_grppfix_head *head) { struct pim_autorp_grppfix *grp; while ((grp = pim_autorp_grppfix_pop(head))) XFREE(MTYPE_PIM_AUTORP_GRPPFIX, grp); } static void pim_autorp_rp_free(struct pim_autorp_rp *rp, bool installed) { event_cancel(&rp->hold_timer); /* Clean up installed RP info */ if (installed) { if (pim_rp_del(rp->autorp->pim, rp->addr, rp->grp, (strlen(rp->grplist) ? rp->grplist : NULL), RP_SRC_AUTORP)) { zlog_warn("%s: Failed to delete RP %pI4", __func__, &rp->addr); } if (strlen(rp->grplist)) { struct prefix_list *pl; pl = prefix_list_lookup(AFI_IP, rp->grplist); if (pl) prefix_list_delete(pl); } } pim_autorp_grppfix_free(&rp->grp_pfix_list); pim_autorp_grppfix_fini(&rp->grp_pfix_list); XFREE(MTYPE_PIM_AUTORP_RP, rp); } static void pim_autorp_rplist_free(struct pim_autorp_rp_head *head, bool installed) { struct pim_autorp_rp *rp; while ((rp = pim_autorp_rp_pop(head))) pim_autorp_rp_free(rp, installed); } static void pim_autorp_free(struct pim_autorp *autorp) { if (PIM_DEBUG_AUTORP) zlog_debug("%s: Freeing PIM AutoRP", __func__); pim_autorp_rplist_free(&(autorp->discovery_rp_list), true); pim_autorp_rp_fini(&(autorp->discovery_rp_list)); pim_autorp_rplist_free(&(autorp->candidate_rp_list), false); pim_autorp_rp_fini(&(autorp->candidate_rp_list)); pim_autorp_rplist_free(&(autorp->mapping_rp_list), false); pim_autorp_rp_fini(&(autorp->mapping_rp_list)); pim_autorp_rplist_free(&(autorp->advertised_rp_list), false); pim_autorp_rp_fini(&(autorp->advertised_rp_list)); if (autorp->announce_pkt) XFREE(MTYPE_PIM_AUTORP_ANNOUNCE, autorp->announce_pkt); } 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_warn("Failed to join group %pI4 on interface %s", &grp, ifp->name); return false; } zlog_info("%s: Joined AutoRP discovery group %pPA on interface %s", __func__, &grp, ifp->name); 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_warn("Failed to join group %pI4 on interface %s", &grp, ifp->name); return errno; } zlog_info("%s: Joined AutoRP announcement group %pPA on interface %s", __func__, &grp, 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_warn("Failed to leave group %pI4 on interface %s", &grp, ifp->name); return false; } zlog_info("%s: Left AutoRP discovery group %pPA on interface %s", __func__, &grp, ifp->name); 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_warn("Failed to leave group %pI4 on interface %s", &grp, ifp->name); return errno; } zlog_info("%s: Left AutoRP announcement group %pPA on interface %s", __func__, &grp, ifp->name); return true; } static bool pim_autorp_setup(int fd) { #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(fd, 1024 * 1024 * 8); #if defined(HAVE_IP_PKTINFO) /* Linux and Solaris IP_PKTINFO */ data = 1; if (setsockopt(fd, PIM_IPPROTO, IP_PKTINFO, &data, data_len)) { zlog_warn("%s: Could not set IP_PKTINFO on socket fd=%d: errno=%d: %s", __func__, fd, errno, safe_strerror(errno)); return false; } #endif if (set_nonblocking(fd) < 0) { zlog_warn("%s: Could not set non blocking on socket fd=%d: errno=%d: %s", __func__, fd, errno, safe_strerror(errno)); return false; } if (sockopt_reuseaddr(fd)) { zlog_warn("%s: Could not set reuse addr on socket fd=%d: errno=%d: %s", __func__, fd, errno, safe_strerror(errno)); return false; } if (setsockopt_ipv4_multicast_loop(fd, 1) < 0) { zlog_warn("%s: Could not enable multicast loopback on socket fd=%d: errno=%d: %s", __func__, fd, errno, safe_strerror(errno)); return false; } if (bind(fd, (const struct sockaddr *)&autorp_addr, sizeof(autorp_addr)) < 0) { zlog_warn("%s: Could not bind socket: %pSUp, fd=%d, errno=%d, %s", __func__, (union sockunion *)&autorp_addr, fd, errno, safe_strerror(errno)); return false; } if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP finished setup", __func__); return true; } static void autorp_ma_rp_holdtime(struct event *evt) { /* Mapping agent 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 from mapping agent: addr=%pI4, grp=%pFX, grplist=%s", __func__, &rp->addr, &rp->grp, (strlen(rp->grplist) ? rp->grplist : "NONE")); pim_autorp_rp_del(&(rp->autorp->mapping_rp_list), rp); pim_autorp_rp_free(rp, false); } static bool autorp_recv_announcement(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 pim_autorp_rp *ma_rp; struct pim_autorp_rp *trp; if (PIM_DEBUG_AUTORP) zlog_debug("%s: Processing AutoRP Announcement (rpcnt=%u, holdtime=%u)", __func__, rpcnt, holdtime); for (i = 0; i < rpcnt; ++i) { if ((buf_size - offset) < AUTORP_RPLEN) { zlog_warn("%s: Failed to parse AutoRP Announcement RP, invalid buffer size (%u < %u)", __func__, (uint32_t)(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 == AUTORP_PIM_V1 || rp->pimver == AUTORP_PIM_VUNKNOWN) { if (PIM_DEBUG_AUTORP) zlog_debug("%s: Ignoring unsupported PIM version (%u) in AutoRP Announcement for RP %pI4", __func__, rp->pimver, (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?? */ if (PIM_DEBUG_AUTORP) zlog_debug("%s: Announcement 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; } /* Store all announced RP's, calculate what to send in discovery when discovery is sent. */ ma_rp = XCALLOC(MTYPE_PIM_AUTORP_RP, sizeof(struct pim_autorp_rp)); memcpy(&(ma_rp->addr), &rp_addr, sizeof(pim_addr)); trp = pim_autorp_rp_add(&(autorp->mapping_rp_list), ma_rp); if (trp == NULL) { /* RP was brand new, finish initializing */ ma_rp->autorp = autorp; ma_rp->holdtime = holdtime; ma_rp->hold_timer = NULL; ma_rp->grplist[0] = '\0'; memset(&(ma_rp->grp), 0, sizeof(ma_rp->grp)); pim_autorp_grppfix_init(&ma_rp->grp_pfix_list); if (PIM_DEBUG_AUTORP) zlog_debug("%s: New candidate RP learned (%pPA)", __func__, &rp_addr); } else { /* Returned an existing entry, free allocated RP */ XFREE(MTYPE_PIM_AUTORP_RP, ma_rp); ma_rp = trp; /* Free the existing group prefix list, in case the advertised groups changed */ pim_autorp_grppfix_free(&ma_rp->grp_pfix_list); } /* Cancel any existing timer and restart it */ event_cancel(&ma_rp->hold_timer); if (holdtime > 0) event_add_timer(router->master, autorp_ma_rp_holdtime, ma_rp, ma_rp->holdtime, &(ma_rp->hold_timer)); if (PIM_DEBUG_AUTORP) zlog_debug("%s: Parsing %u group(s) for candidate RP %pPA", __func__, rp->grpcnt, &rp_addr); for (j = 0; j < rp->grpcnt; ++j) { /* grp is already pointing at the first group in the buffer */ struct pim_autorp_grppfix *lgrp; struct pim_autorp_grppfix *tgrp; if ((buf_size - offset) < AUTORP_GRPLEN) { zlog_warn("%s: Failed parsing AutoRP announcement, RP(%pI4), invalid buffer size (%u < %u)", __func__, &rp_addr, (uint32_t)(buf_size - offset), AUTORP_GRPLEN); return false; } grp = (struct autorp_pkt_grp *)(buf + offset); offset += AUTORP_GRPLEN; lgrp = XCALLOC(MTYPE_PIM_AUTORP_GRPPFIX, sizeof(struct pim_autorp_grppfix)); lgrp->grp.family = AF_INET; lgrp->grp.prefixlen = grp->masklen; lgrp->grp.u.prefix4.s_addr = grp->addr; lgrp->negative = grp->negprefix; if (PIM_DEBUG_AUTORP) zlog_debug("%s: %s%pFX added to candidate RP %pPA", __func__, (lgrp->negative ? "!" : ""), &lgrp->grp, &rp_addr); tgrp = pim_autorp_grppfix_add(&ma_rp->grp_pfix_list, lgrp); if (tgrp != NULL) { /* This should never happen but if there was an existing entry just free the * allocated group prefix */ if (PIM_DEBUG_AUTORP) zlog_debug("%s: %pFX was duplicated in AutoRP announcement", __func__, &lgrp->grp); XFREE(MTYPE_PIM_AUTORP_GRPPFIX, lgrp); } } } if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP processed announcement message", __func__); return true; } static void autorp_cand_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, true); } 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; int ret; ret = pim_rp_new(autorp->pim, rpaddr, grp, listname, RP_SRC_AUTORP); /* There may not be a path to the RP right now, but that doesn't mean it failed to add the RP */ if (ret != PIM_SUCCESS && ret != PIM_RP_NO_PATH) { zlog_warn("%s: Failed to add active RP addr=%pI4, grp=%pFX, grplist=%s", __func__, &rpaddr, &grp, (listname ? listname : "NONE")); return false; } rp = XCALLOC(MTYPE_PIM_AUTORP_RP, sizeof(*rp)); rp->autorp = autorp; memcpy(&(rp->addr), &rpaddr, sizeof(pim_addr)); 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); /* Make sure the timer is NULL so the cancel below doesn't mess up */ trp->hold_timer = NULL; zlog_info("%s: Added new AutoRP learned RP addr=%pI4, grp=%pFX, grplist=%s", __func__, &rpaddr, &grp, (listname ? listname : "NONE")); } else { /* RP already existed, free the temp one */ XFREE(MTYPE_PIM_AUTORP_RP, rp); } /* Cancel any existing timer before restarting it */ event_cancel(&trp->hold_timer); 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_cand_rp_holdtime, trp, holdtime, &(trp->hold_timer)); if (PIM_DEBUG_AUTORP) zlog_debug("%s: Started %u second hold timer for RP %pI4", __func__, holdtime, &trp->addr); } return true; } static size_t autorp_build_disc_rps(struct pim_autorp *autorp, uint8_t *buf, size_t buf_sz, size_t *sz) { /* Header has already been added, fill in starting with the address of RP1 * buf_sz is the max size of the buf * sz is the current size of the packet, update as buf is filled * return the total number of RP's added * * * We need to resolve the announced RP's following these rules: * 1) Co-existence of longer and shorter group prefixes, from different RPs. E.g. when RP1 * announces 224.2.*.*, and RP2 announces 224.2.2.*, both are accepted; * 2) For announcements for identical group prefixes from two different RPs, the one from the * RP with the higher IP address is accepted; * 3) No duplicates are sent to the AUTORP-DISCOVERY address. E.g. if an RP announces both * 224.2.2.* and 224.2.*.*, the former group-prefix is not sent and only 224.2.*.* is sent * to the AUTORP-DISCOVERY address. * * * The approach to resolution, first loop the stored RP's and extract the group prefixes, stored * in a sorted list, sorted from least specific to most 0.0.0.0/0 -> 239.255.255.255/32. Each * group prefix will then store the RP advertising that group prefix, this will resolve 2. * The next step is to then loop the group prefix list and store them back into a list sorted by * RP address, where the least specific group address will be stored, resolving 3. 1 is more * about what is allowed, and in the example above the different prefixes will be unique in the * list of group prefixes, and when they go back into RP's, they are also from different RP's * and will therefore be sent. */ struct pim_autorp_rp *rp; struct pim_autorp_rp *trp; struct pim_autorp_grppfix *grp; struct pim_autorp_grppfix *grp2; struct pim_autorp_grppfix *tgrp; struct pim_autorp_grppfix_head grplist; bool skip = false; size_t rpcnt = 0; size_t bsz = 0; /* Initialize the lists, grplist is temporary, disc rp list is stored long term for * show output, so make sure it's empty */ pim_autorp_grppfix_init(&grplist); pim_autorp_rplist_free(&autorp->advertised_rp_list, false); /* Loop the advertised RP's and their group prefixes and make a unique list of group prefixes, * keeping just the highest IP RP for each group prefix */ frr_each (pim_autorp_rp, &autorp->mapping_rp_list, rp) { frr_each (pim_autorp_grppfix, &rp->grp_pfix_list, grp) { grp2 = XCALLOC(MTYPE_PIM_AUTORP_GRPPFIX, sizeof(struct pim_autorp_grppfix)); prefix_copy(&grp2->grp, &grp->grp); grp2->negative = grp->negative; grp2->rp = rp->addr; tgrp = pim_autorp_grppfix_add(&grplist, grp2); if (tgrp != NULL) { /* Returned an existing entry. Use the highest RP addr and free allocated object */ if (IPV4_ADDR_CMP(&tgrp->rp, &grp2->rp)) tgrp->rp = grp2->rp; XFREE(MTYPE_PIM_AUTORP_GRPPFIX, grp2); } } } /* Now loop the unique group prefixes and put it back into an RP list */ frr_each (pim_autorp_grppfix, &grplist, grp) { rp = XCALLOC(MTYPE_PIM_AUTORP_RP, sizeof(struct pim_autorp_rp)); rp->addr = grp->rp; trp = pim_autorp_rp_add(&autorp->advertised_rp_list, rp); if (trp == NULL) { /* RP was brand new, finish initializing */ rp->autorp = NULL; rp->holdtime = 0; rp->hold_timer = NULL; rp->grplist[0] = '\0'; memset(&(rp->grp), 0, sizeof(rp->grp)); pim_autorp_grppfix_init(&rp->grp_pfix_list); } else { /* Returned an existing entry, free allocated RP */ XFREE(MTYPE_PIM_AUTORP_RP, rp); rp = trp; } /* Groups are in order from least specific to most, so go through the existing * groups for this RP and see if the current group is within the prefix of one that * is already in the list, if so, skip it, if not, add it * If one is a positive match and the other is negative, then still include it. */ skip = false; frr_each (pim_autorp_grppfix, &rp->grp_pfix_list, grp2) { if (prefix_match(&grp2->grp, &grp->grp) && grp->negative == grp2->negative) { skip = true; break; } } if (skip) continue; /* add the group to the RP's group list */ grp2 = XCALLOC(MTYPE_PIM_AUTORP_GRPPFIX, sizeof(struct pim_autorp_grppfix)); prefix_copy(&grp2->grp, &grp->grp); grp2->negative = grp->negative; tgrp = pim_autorp_grppfix_add(&rp->grp_pfix_list, grp2); assert(tgrp == NULL); } /* Done with temporary group prefix list, so free and finish */ pim_autorp_grppfix_free(&grplist); pim_autorp_grppfix_fini(&grplist); /* Now finally we can loop the disc rp list and build the packet */ frr_each (pim_autorp_rp, &autorp->advertised_rp_list, rp) { struct autorp_pkt_rp *brp; struct autorp_pkt_grp *bgrp; size_t rp_sz; size_t grpcnt; grpcnt = pim_autorp_grppfix_count(&rp->grp_pfix_list); rp_sz = sizeof(struct autorp_pkt_rp) + (grpcnt * sizeof(struct autorp_pkt_grp)); if (buf_sz < *sz + rp_sz) { if (PIM_DEBUG_AUTORP) zlog_debug("%s: Failed to pack AutoRP discovery packet, buffer overrun, (%u < %u)", __func__, (uint32_t)buf_sz, (uint32_t)(*sz + rp_sz)); break; } if (PIM_DEBUG_AUTORP) zlog_debug("%s: Add RP %pI4 (grpcnt=%u) to discovery message", __func__, &rp->addr, (uint32_t)grpcnt); rpcnt++; brp = (struct autorp_pkt_rp *)(buf + bsz); bsz += sizeof(struct autorp_pkt_rp); /* Since this is an in_addr, assume it's already the right byte order */ brp->addr = rp->addr.s_addr; brp->pimver = AUTORP_PIM_V2; brp->reserved = 0; brp->grpcnt = grpcnt; frr_each (pim_autorp_grppfix, &rp->grp_pfix_list, grp) { bgrp = (struct autorp_pkt_grp *)(buf + bsz); bsz += sizeof(struct autorp_pkt_grp); bgrp->addr = grp->grp.u.prefix4.s_addr; bgrp->masklen = grp->grp.prefixlen; bgrp->negprefix = grp->negative; if (PIM_DEBUG_AUTORP) zlog_debug("%s: Add group %s%pFX for RP %pI4 to discovery message", __func__, (grp->negative ? "!" : ""), &grp->grp, &rp->addr); } /* Update the size with this RP now that it is packed */ *sz += bsz; } return rpcnt; } static size_t autorp_build_disc_packet(struct pim_autorp *autorp, uint8_t *buf, size_t buf_sz) { size_t sz = 0; struct autorp_pkt_hdr *hdr; if (buf_sz >= AUTORP_HDRLEN) { hdr = (struct autorp_pkt_hdr *)buf; hdr->version = AUTORP_VERSION; hdr->type = AUTORP_DISCOVERY_TYPE; hdr->holdtime = htons(autorp->discovery_holdtime); hdr->reserved = 0; sz += AUTORP_HDRLEN; hdr->rpcnt = autorp_build_disc_rps(autorp, buf + sizeof(struct autorp_pkt_hdr), (buf_sz - AUTORP_HDRLEN), &sz); if (hdr->rpcnt == 0) sz = 0; } return sz; } static void autorp_send_discovery(struct event *evt) { struct pim_autorp *autorp = EVENT_ARG(evt); struct sockaddr_in discGrp; size_t disc_sz; size_t buf_sz = 65535; uint8_t buf[65535] = { 0 }; if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP sending discovery info", __func__); /* Mark true, even if nothing is sent */ autorp->mapping_agent_active = true; disc_sz = autorp_build_disc_packet(autorp, buf, buf_sz); if (disc_sz > 0) { discGrp.sin_family = AF_INET; discGrp.sin_port = htons(PIM_AUTORP_PORT); inet_pton(PIM_AF, PIM_AUTORP_DISCOVERY_GRP, &discGrp.sin_addr); if (setsockopt(autorp->sock, IPPROTO_IP, IP_MULTICAST_TTL, &(autorp->discovery_scope), sizeof(autorp->discovery_scope)) == 0) { if (setsockopt(autorp->sock, IPPROTO_IP, IP_MULTICAST_IF, &(autorp->mapping_agent_addrsel.run_addr), sizeof(autorp->mapping_agent_addrsel.run_addr)) == 0) { if (sendto(autorp->sock, buf, disc_sz, 0, (struct sockaddr *)&discGrp, sizeof(discGrp)) > 0) { if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP discovery message sent", __func__); } else if (PIM_DEBUG_AUTORP) zlog_warn("%s: Failed to send AutoRP discovery message, errno=%d, %s", __func__, errno, safe_strerror(errno)); } else if (PIM_DEBUG_AUTORP) zlog_warn("%s: Failed to set Multicast Interface for sending AutoRP discovery message, errno=%d, %s", __func__, errno, safe_strerror(errno)); } else if (PIM_DEBUG_AUTORP) zlog_warn("%s: Failed to set Multicast TTL for sending AutoRP discovery message, errno=%d, %s", __func__, errno, safe_strerror(errno)); } /* Start the new timer for the entire send discovery interval */ event_add_timer(router->master, autorp_send_discovery, autorp, autorp->discovery_interval, &(autorp->send_discovery_timer)); } static void autorp_send_discovery_on(struct pim_autorp *autorp) { int interval = 5; /* Send the first discovery shortly after being enabled. * If the configured interval is less than 5 seconds, then just use that. */ if (interval > autorp->discovery_interval) interval = autorp->discovery_interval; if (autorp->send_discovery_timer) if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP discovery sending enabled in %u seconds", __func__, interval); event_add_timer(router->master, autorp_send_discovery, autorp, interval, &(autorp->send_discovery_timer)); } static void autorp_send_discovery_off(struct pim_autorp *autorp) { if (autorp->send_discovery_timer) if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP discovery sending disabled", __func__); event_cancel(&(autorp->send_discovery_timer)); } static bool autorp_recv_discovery(struct pim_autorp *autorp, uint8_t rpcnt, uint16_t holdtime, char *buf, size_t buf_size, pim_addr src) { 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; if (PIM_DEBUG_AUTORP) zlog_debug("%s: Received AutoRP discovery message (src=%pI4, rpcnt=%u, holdtime=%u)", __func__, &src, rpcnt, holdtime); if (autorp->send_rp_discovery && (pim_addr_cmp(autorp->mapping_agent_addrsel.run_addr, src) < 0)) { if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP send discovery suppressed -- Discovery received with higher IP address", __func__); /* Cancel the existing send timer and restart for 3X the send discovery interval */ event_cancel(&(autorp->send_discovery_timer)); event_add_timer(router->master, autorp_send_discovery, autorp, (autorp->discovery_interval * 3), &(autorp->send_discovery_timer)); /* Clear the last sent discovery RP's, since it is no longer valid */ pim_autorp_rplist_free(&autorp->advertised_rp_list, false); /* Unset flag indicating we are active */ autorp->mapping_agent_active = false; } for (i = 0; i < rpcnt; ++i) { if ((buf_size - offset) < AUTORP_RPLEN) { zlog_warn("%s: Failed to parse AutoRP discovery message, invalid buffer size (%u < %u)", __func__, (uint32_t)(buf_size - offset), AUTORP_RPLEN); return false; } rp = (struct autorp_pkt_rp *)(buf + offset); offset += AUTORP_RPLEN; rp_addr.s_addr = rp->addr; if (PIM_DEBUG_AUTORP) zlog_debug("%s: Parsing RP %pI4 (grpcnt=%u)", __func__, (in_addr_t *)&rp->addr, rp->grpcnt); /* Ignore RP's limited to PIM version 1 or with an unknown version */ if (rp->pimver == AUTORP_PIM_V1 || rp->pimver == AUTORP_PIM_VUNKNOWN) { if (PIM_DEBUG_AUTORP) zlog_debug("%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?? */ if (PIM_DEBUG_AUTORP) zlog_debug("%s: Discovery message has no groups for RP %pI4", __func__, (in_addr_t *)&(rp->addr)); continue; } /* Make sure there is enough buffer to parse all the groups */ if ((buf_size - offset) < (AUTORP_GRPLEN * rp->grpcnt)) { if (PIM_DEBUG_AUTORP) zlog_debug("%s: Buffer underrun parsing groups for RP %pI4 (%u < %u)", __func__, (in_addr_t *)&(rp->addr), (uint32_t)(buf_size - offset), (uint32_t)(AUTORP_GRPLEN * rp->grpcnt)); return false; } /* Get the first group so we can check for a negative prefix */ /* Don't add to offset yet to make the multiple group loop easier */ grp = (struct autorp_pkt_grp *)(buf + offset); if (rp->grpcnt == 1 && grp->negprefix == 0) { /* Only one group with positive prefix, we can use the standard RP API */ offset += AUTORP_GRPLEN; grppfix.family = AF_INET; grppfix.prefixlen = grp->masklen; grppfix.u.prefix4.s_addr = grp->addr; if (PIM_DEBUG_AUTORP) zlog_debug("%s: Parsing group %s%pFX for RP %pI4", __func__, (grp->negprefix ? "!" : ""), &grppfix, (in_addr_t *)&rp->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_lookup(AFI_IP, plname); if (pl) { /* Existing prefix list found, delete it first */ /* TODO: Instead of deleting completely, maybe we can just clear it and re-add entries */ if (PIM_DEBUG_AUTORP) zlog_debug("%s: Found existing prefix list %s, replacing it", __func__, plname); prefix_list_delete(pl); } /* Now get a new prefix list */ pl = prefix_list_get(AFI_IP, 0, plname); for (j = 0; j < rp->grpcnt; ++j) { /* This will just set grp to the same pointer on the first loop, but offset will * be updated correctly while parsing */ grp = (struct autorp_pkt_grp *)(buf + offset); offset += AUTORP_GRPLEN; 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 (PIM_DEBUG_AUTORP) zlog_debug("%s: Parsing group %s%pFX for RP %pI4", __func__, (grp->negprefix ? "!" : ""), &ple->prefix, (in_addr_t *)&rp->addr); } if (!pim_autorp_add_rp(autorp, rp_addr, grppfix, plname, holdtime)) success = false; } } return success; } static bool autorp_recv_msg(struct pim_autorp *autorp, char *buf, size_t buf_size, pim_addr src) { struct autorp_pkt_hdr *h; if (PIM_DEBUG_AUTORP) zlog_debug("%s: Received AutoRP message", __func__); if (buf_size < AUTORP_HDRLEN) { zlog_warn("%s: Invalid AutoRP Header size (%u < %u)", __func__, (uint32_t)buf_size, AUTORP_HDRLEN); return false; } h = (struct autorp_pkt_hdr *)buf; if (h->version != AUTORP_VERSION) { zlog_warn("%s: Unsupported AutoRP version (%u != %u)", __func__, h->version, AUTORP_VERSION); return false; } if (h->type == AUTORP_ANNOUNCEMENT_TYPE) return autorp_recv_announcement(autorp, h->rpcnt, htons(h->holdtime), buf + AUTORP_HDRLEN, buf_size - AUTORP_HDRLEN); if (h->type == AUTORP_DISCOVERY_TYPE) return autorp_recv_discovery(autorp, h->rpcnt, htons(h->holdtime), buf + AUTORP_HDRLEN, buf_size - AUTORP_HDRLEN, src); zlog_warn("%s: Unknown AutoRP message type (%u)", __func__, h->type); return false; } 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; struct sockaddr_storage from; socklen_t fromlen = sizeof(from); pim_addr src; if (PIM_DEBUG_AUTORP) zlog_debug("%s: Reading from AutoRP socket", __func__); while (1) { rd = pim_socket_recvfromto(fd, (uint8_t *)buf, sizeof(buf), &from, &fromlen, 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 (from.ss_family == AF_INET) src.s_addr = ((struct sockaddr_in *)&from)->sin_addr.s_addr; else { zlog_warn("%s: AutoRP message is not IPV4", __func__); goto err; } if (!autorp_recv_msg(autorp, buf, rd, src)) zlog_warn("%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; } if (!pim_autorp_setup(fd)) { zlog_warn("Could not setup autorp socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); close(fd); return false; } } autorp->sock = fd; if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP socket enabled (fd=%u)", __func__, fd); 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->announce_pkt_sz >= MIN_AUTORP_PKT_SZ) { if (PIM_DEBUG_AUTORP) zlog_debug("%s: Sending AutoRP announcement", __func__); if (setsockopt(autorp->sock, IPPROTO_IP, IP_MULTICAST_TTL, &(autorp->announce_scope), sizeof(autorp->announce_scope)) < 0) { zlog_warn("%s: Failed to set Multicast TTL for sending AutoRP announcement message, errno=%d, %s", __func__, errno, safe_strerror(errno)); return; } 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)) { if (setsockopt(autorp->sock, IPPROTO_IP, IP_MULTICAST_IF, &(pim_ifp->primary_address), sizeof(pim_ifp->primary_address)) < 0) { zlog_warn("%s: Failed to set Multicast Interface for sending AutoRP announcement message, errno=%d, %s", __func__, errno, safe_strerror(errno)); continue; } if (sendto(autorp->sock, autorp->announce_pkt, autorp->announce_pkt_sz, 0, (struct sockaddr *)&announceGrp, sizeof(announceGrp)) <= 0) zlog_warn("%s: Failed to send AutoRP announcement message, errno=%d, %s", __func__, errno, safe_strerror(errno)); } } } /* 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; /* Send the first announcement shortly after being enabled. * If the configured interval is less than 5 seconds, then just use that. */ if (interval > autorp->announce_interval) interval = autorp->announce_interval; if (autorp->announce_timer == NULL) if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP announcement sending enabled", __func__); event_add_timer(router->master, autorp_send_announcement, autorp, interval, &(autorp->announce_timer)); } static void autorp_announcement_off(struct pim_autorp *autorp) { if (autorp->announce_timer != NULL) if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP announcement sending disabled", __func__); event_cancel(&(autorp->announce_timer)); } /* 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 autorp_pkt_grp *grpp = (struct autorp_pkt_grp *)buf; uint8_t cnt = 0; 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 */ struct prefix_list *plist; struct prefix_list_entry *ple; plist = prefix_list_lookup(AFI_IP, rp->grplist); for (ple = plist->head; ple; ple = ple->next) { if (pim_addr_is_multicast(ple->prefix.u.prefix4) && ple->prefix.prefixlen >= 4) { 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 = AUTORP_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; 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_DEBUG_AUTORP) zlog_debug("%s: Evaluating AutoRP candidate %pI4, group range %pFX, group list %s", __func__, &rp->addr, &rp->grp, rp->grplist); if (pim_addr_is_any(rp->addr) || (is_default_prefix(&rp->grp) && strlen(rp->grplist) == 0)) continue; /* Make sure that either group prefix is set, or that the prefix list exists and has at * least one valid multicast prefix in it. Only multicast prefixes will be used. */ if (is_default_prefix(&rp->grp)) { struct prefix_list *plist; struct prefix_list_entry *ple; 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) { if (pim_addr_is_multicast(ple->prefix.u.prefix4) && ple->prefix.prefixlen >= 4) 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 (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP candidate %pI4 added to announcement", __func__, &rp->addr); } 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); /* * 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) */ if (!autorp->announce_pkt) autorp->announce_pkt = XCALLOC(MTYPE_PIM_AUTORP_ANNOUNCE, 65536); autorp->announce_pkt_sz = 0; holdtime = autorp->announce_holdtime; if (holdtime == DEFAULT_AUTORP_ANNOUNCE_HOLDTIME) holdtime = autorp->announce_interval * 3; if (holdtime > UINT16_MAX) holdtime = UINT16_MAX; hdr = (struct autorp_pkt_hdr *)autorp->announce_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->announce_pkt + sizeof(struct autorp_pkt_hdr), &(autorp->announce_pkt_sz)); /* Still need to add on the size of the header */ autorp->announce_pkt_sz += sizeof(struct autorp_pkt_hdr); /* Only turn on the announcement timer if we have a packet to send */ if (autorp->announce_pkt_sz >= MIN_AUTORP_PKT_SZ) autorp_announcement_on(autorp); } void pim_autorp_prefix_list_update(struct pim_instance *pim, struct prefix_list *plist) { struct pim_autorp_rp *rp = NULL; struct pim_autorp *autorp = NULL; autorp = pim->autorp; if (autorp == NULL) return; /* Search for a candidate RP using this prefix list */ frr_each_safe (pim_autorp_rp, &(autorp->candidate_rp_list), rp) { if (strmatch(rp->grplist, plist->name)) break; } /* If we broke out of the loop early because we found a match, then rebuild the announcement */ if (rp != NULL) pim_autorp_new_announcement(pim); } 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, false); 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_RP, 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_RP, 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)); rp->grp.family = AF_INET; 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_AUTORP_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_AUTORP_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_send_discovery_apply(struct pim_autorp *autorp) { if (!autorp->mapping_agent_addrsel.run || !autorp->send_rp_discovery) { autorp_send_discovery_off(autorp); return; } autorp_send_discovery_on(autorp); } 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_warn("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_warn("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; autorp->send_discovery_timer = NULL; autorp->send_rp_discovery = false; pim_autorp_rp_init(&(autorp->discovery_rp_list)); pim_autorp_rp_init(&(autorp->candidate_rp_list)); pim_autorp_rp_init(&(autorp->mapping_rp_list)); pim_autorp_rp_init(&autorp->advertised_rp_list); autorp->announce_scope = DEFAULT_AUTORP_ANNOUNCE_SCOPE; autorp->announce_interval = DEFAULT_AUTORP_ANNOUNCE_INTERVAL; autorp->announce_holdtime = DEFAULT_AUTORP_ANNOUNCE_HOLDTIME; autorp->discovery_scope = DEFAULT_AUTORP_DISCOVERY_SCOPE; autorp->discovery_interval = DEFAULT_AUTORP_DISCOVERY_INTERVAL; autorp->discovery_holdtime = DEFAULT_AUTORP_DISCOVERY_HOLDTIME; cand_addrsel_clear(&(autorp->mapping_agent_addrsel)); pim->autorp = autorp; if (!pim_autorp_socket_enable(autorp)) { zlog_warn("%s: AutoRP failed to initialize, feature will not work correctly", __func__); return; } 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); autorp_announcement_off(autorp); autorp_send_discovery_off(autorp); pim_autorp_free(autorp); pim_autorp_socket_disable(autorp); XFREE(MTYPE_PIM_AUTORP, pim->autorp); if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP Finished", __func__); } int pim_autorp_config_write(struct pim_instance *pim, struct vty *vty) { struct pim_autorp_rp *rp; struct pim_autorp *autorp = pim->autorp; int writes = 0; if (!autorp->do_discovery) { vty_out(vty, " no autorp discovery\n"); ++writes; } if (autorp->announce_interval != DEFAULT_AUTORP_ANNOUNCE_INTERVAL || autorp->announce_scope != DEFAULT_AUTORP_ANNOUNCE_SCOPE || autorp->announce_holdtime != DEFAULT_AUTORP_ANNOUNCE_HOLDTIME) { vty_out(vty, " autorp announce"); if (autorp->announce_interval != DEFAULT_AUTORP_ANNOUNCE_INTERVAL) vty_out(vty, " interval %u", autorp->announce_interval); if (autorp->announce_scope != DEFAULT_AUTORP_ANNOUNCE_SCOPE) vty_out(vty, " scope %u", autorp->announce_scope); if (autorp->announce_holdtime != DEFAULT_AUTORP_ANNOUNCE_HOLDTIME) vty_out(vty, " holdtime %u", autorp->announce_holdtime); vty_out(vty, "\n"); ++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; vty_out(vty, " autorp announce %pI4", &(rp->addr)); if (!is_default_prefix(&(rp->grp))) vty_out(vty, " %pFX", &(rp->grp)); else vty_out(vty, " group-list %s", rp->grplist); vty_out(vty, "\n"); ++writes; } if (autorp->send_rp_discovery) { if (autorp->mapping_agent_addrsel.cfg_enable) { vty_out(vty, " autorp send-rp-discovery"); switch (autorp->mapping_agent_addrsel.cfg_mode) { case CAND_ADDR_LO: break; case CAND_ADDR_ANY: vty_out(vty, " source any"); break; case CAND_ADDR_IFACE: vty_out(vty, " source interface %s", autorp->mapping_agent_addrsel.cfg_ifname); break; case CAND_ADDR_EXPLICIT: vty_out(vty, " source address %pPA", &autorp->mapping_agent_addrsel.cfg_addr); break; } vty_out(vty, "\n"); ++writes; } if (autorp->discovery_interval != DEFAULT_AUTORP_DISCOVERY_INTERVAL || autorp->discovery_scope != DEFAULT_AUTORP_DISCOVERY_SCOPE || autorp->discovery_holdtime != DEFAULT_AUTORP_DISCOVERY_HOLDTIME) { vty_out(vty, " autorp send-rp-discovery"); if (autorp->discovery_interval != DEFAULT_AUTORP_DISCOVERY_INTERVAL) vty_out(vty, " interval %u", autorp->discovery_interval); if (autorp->discovery_scope != DEFAULT_AUTORP_DISCOVERY_SCOPE) vty_out(vty, " scope %u", autorp->discovery_scope); if (autorp->discovery_holdtime != DEFAULT_AUTORP_DISCOVERY_HOLDTIME) vty_out(vty, " holdtime %u", autorp->discovery_holdtime); vty_out(vty, "\n"); ++writes; } } return writes; } static void pim_autorp_show_autorp_json(struct pim_autorp *autorp, const char *component, json_object *json, struct ttable *cand_table) { struct pim_autorp_rp *rp; if (!component || strmatch(component, "discovery")) { json_object *disc_obj; disc_obj = json_object_new_object(); json_object_boolean_add(disc_obj, "enabled", autorp->do_discovery); if (autorp->do_discovery) { json_object *rplist_obj; rplist_obj = json_object_new_object(); frr_each (pim_autorp_rp, &(autorp->discovery_rp_list), rp) { json_object *rp_obj; json_object *grp_arr; rp_obj = json_object_new_object(); json_object_string_addf(rp_obj, "rpAddress", "%pI4", &rp->addr); json_object_int_add(rp_obj, "holdtime", rp->holdtime); grp_arr = json_object_new_array(); if (strlen(rp->grplist)) { struct prefix_list *pl; struct prefix_list_entry *ple; pl = prefix_list_lookup(AFI_IP, rp->grplist); if (pl == NULL) continue; for (ple = pl->head; ple != NULL; ple = ple->next) { json_object *grp_obj; grp_obj = json_object_new_object(); json_object_boolean_add(grp_obj, "negative", ple->type == PREFIX_DENY); json_object_string_addf(grp_obj, "prefix", "%pFX", &ple->prefix); json_object_array_add(grp_arr, grp_obj); } } else { json_object *grp_obj; grp_obj = json_object_new_object(); json_object_boolean_add(grp_obj, "negative", false); json_object_string_addf(grp_obj, "prefix", "%pFX", &rp->grp); json_object_array_add(grp_arr, grp_obj); } json_object_object_add(rp_obj, "groupRanges", grp_arr); json_object_object_addf(rplist_obj, rp_obj, "%pI4", &rp->addr); } json_object_object_add(disc_obj, "rpList", rplist_obj); } json_object_object_add(json, "discovery", disc_obj); } if (!component || strmatch(component, "candidate")) { json_object *announce_obj; announce_obj = json_object_new_object(); json_object_boolean_add(announce_obj, "enabled", pim_autorp_rp_count(&autorp->candidate_rp_list) > 0); if (pim_autorp_rp_count(&autorp->candidate_rp_list) > 0) { json_object_int_add(announce_obj, "scope", autorp->announce_scope); json_object_int_add(announce_obj, "interval", autorp->announce_interval); json_object_int_add(announce_obj, "holdtime", (autorp->announce_holdtime == DEFAULT_AUTORP_ANNOUNCE_HOLDTIME ? (autorp->announce_interval * 3) : autorp->announce_holdtime)); json_object_object_add(announce_obj, "rpList", ttable_json_with_json_text(cand_table, "sss", "rpAddress|groupRange|prefixList")); } json_object_object_add(json, "announce", announce_obj); } if (!component || strmatch(component, "mapping-agent")) { json_object *adv_obj; adv_obj = json_object_new_object(); json_object_boolean_add(adv_obj, "enabled", autorp->send_rp_discovery); if (autorp->send_rp_discovery) { json_object *rplist_obj; json_object_boolean_add(adv_obj, "active", autorp->mapping_agent_active); json_object_int_add(adv_obj, "scope", autorp->discovery_scope); json_object_int_add(adv_obj, "interval", autorp->discovery_interval); json_object_int_add(adv_obj, "holdtime", autorp->discovery_holdtime); switch (autorp->mapping_agent_addrsel.cfg_mode) { case CAND_ADDR_LO: json_object_string_add(adv_obj, "source", "loopback"); break; case CAND_ADDR_ANY: json_object_string_add(adv_obj, "source", "any"); break; case CAND_ADDR_IFACE: json_object_string_add(adv_obj, "source", "interface"); json_object_string_add(adv_obj, "interface", autorp->mapping_agent_addrsel.cfg_ifname); break; case CAND_ADDR_EXPLICIT: json_object_string_add(adv_obj, "source", "address"); break; } json_object_string_addf(adv_obj, "address", "%pPA", &autorp->mapping_agent_addrsel.run_addr); rplist_obj = json_object_new_object(); frr_each (pim_autorp_rp, &(autorp->advertised_rp_list), rp) { json_object *rp_obj; json_object *grp_arr; struct pim_autorp_grppfix *grppfix; rp_obj = json_object_new_object(); json_object_string_addf(rp_obj, "rpAddress", "%pI4", &rp->addr); grp_arr = json_object_new_array(); frr_each (pim_autorp_grppfix, &rp->grp_pfix_list, grppfix) { json_object *grp_obj; grp_obj = json_object_new_object(); json_object_boolean_add(grp_obj, "negative", grppfix->negative); json_object_string_addf(grp_obj, "prefix", "%pFX", &grppfix->grp); json_object_array_add(grp_arr, grp_obj); } json_object_object_add(rp_obj, "groupRanges", grp_arr); json_object_object_addf(rplist_obj, rp_obj, "%pI4", &rp->addr); } json_object_object_add(adv_obj, "rpList", rplist_obj); } json_object_object_add(json, "mapping-agent", adv_obj); } } void pim_autorp_show_autorp(struct vty *vty, struct pim_instance *pim, const char *component, json_object *json) { struct pim_autorp *autorp = pim->autorp; struct pim_autorp_rp *rp; struct ttable *cand_table = NULL; struct ttable *adv_table = NULL; struct ttable *disc_table = NULL; char *tmp; if (autorp == NULL) return; /* We may use the candidate table in the json output, so prepare it first. */ if (!component || strmatch(component, "candidate")) { cand_table = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ttable_add_row(cand_table, "RP address|Group Range|Prefix-List"); cand_table->style.cell.rpad = 2; cand_table->style.corner = '+'; ttable_restyle(cand_table); frr_each (pim_autorp_rp, &(autorp->candidate_rp_list), rp) { if (strlen(rp->grplist)) ttable_add_row(cand_table, "%pI4|%s|%s", &(rp->addr), "-", rp->grplist); else ttable_add_row(cand_table, "%pI4|%pFX|%s", &(rp->addr), &(rp->grp), "-"); } } if (json) { pim_autorp_show_autorp_json(autorp, component, json, cand_table); if (cand_table) ttable_del(cand_table); return; } /* Prepare discovered RP's table. */ if (!component || strmatch(component, "discovery")) { disc_table = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ttable_add_row(disc_table, "RP address|Group Range"); disc_table->style.cell.rpad = 2; disc_table->style.corner = '+'; ttable_restyle(disc_table); frr_each (pim_autorp_rp, &(autorp->discovery_rp_list), rp) { if (strlen(rp->grplist)) { struct prefix_list *pl; struct prefix_list_entry *ple; bool first = true; pl = prefix_list_lookup(AFI_IP, rp->grplist); if (pl == NULL) { ttable_add_row(disc_table, "%pI4|failed to find prefix list %s", &(rp->addr), rp->grplist); continue; } for (ple = pl->head; ple != NULL; ple = ple->next) { if (first) ttable_add_row(disc_table, "%pI4|%s%pFX", &(rp->addr), (ple->type == PREFIX_DENY ? "!" : " "), &ple->prefix); else ttable_add_row(disc_table, "%s|%s%pFX", " ", (ple->type == PREFIX_DENY ? "!" : " "), &ple->prefix); first = false; } } else ttable_add_row(disc_table, "%pI4| %pFX", &(rp->addr), &(rp->grp)); } } /* Prepare discovery RP's table (mapping-agent). */ if (!component || strmatch(component, "mapping-agent")) { adv_table = ttable_new(&ttable_styles[TTSTYLE_BLANK]); ttable_add_row(adv_table, "RP address|Group Range"); adv_table->style.cell.rpad = 2; adv_table->style.corner = '+'; ttable_restyle(adv_table); frr_each (pim_autorp_rp, &(autorp->advertised_rp_list), rp) { struct pim_autorp_grppfix *grppfix; bool first = true; frr_each (pim_autorp_grppfix, &rp->grp_pfix_list, grppfix) { if (first) ttable_add_row(adv_table, "%pI4|%s%pFX", &rp->addr, grppfix->negative ? "!" : " ", &grppfix->grp); else ttable_add_row(adv_table, "%s|%s%pFX", " ", grppfix->negative ? "!" : " ", &grppfix->grp); first = false; } } } if (!component || strmatch(component, "discovery")) { vty_out(vty, "AutoRP Discovery is %sabled\n", (autorp->do_discovery ? "en" : "dis")); if (autorp->do_discovery) { tmp = ttable_dump(disc_table, "\n"); vty_out(vty, "\n"); vty_out(vty, "Discovered RP's (count=%u)\n", (uint32_t)pim_autorp_rp_count(&autorp->discovery_rp_list)); vty_out(vty, "%s\n", tmp); XFREE(MTYPE_TMP_TTABLE, tmp); } else vty_out(vty, "\n"); } if (!component || strmatch(component, "candidate")) { vty_out(vty, "AutoRP Announcement is %sabled\n", (pim_autorp_rp_count(&autorp->candidate_rp_list) > 0 ? "en" : "dis")); if (pim_autorp_rp_count(&autorp->candidate_rp_list) > 0) { tmp = ttable_dump(cand_table, "\n"); vty_out(vty, " interval %us scope %u holdtime %us\n", autorp->announce_interval, autorp->announce_scope, (autorp->announce_holdtime == DEFAULT_AUTORP_ANNOUNCE_HOLDTIME ? (autorp->announce_interval * 3) : autorp->announce_holdtime)); vty_out(vty, "\n"); vty_out(vty, "Candidate RP's (count=%u)\n", (uint32_t)pim_autorp_rp_count(&autorp->candidate_rp_list)); vty_out(vty, "%s\n", tmp); XFREE(MTYPE_TMP_TTABLE, tmp); } else vty_out(vty, "\n"); } if (!component || strmatch(component, "mapping-agent")) { vty_out(vty, "AutoRP Mapping-Agent is %sabled\n", (autorp->send_rp_discovery ? "en" : "dis")); if (autorp->send_rp_discovery) { vty_out(vty, " interval %us scope %u holdtime %us\n", autorp->discovery_interval, autorp->discovery_scope, autorp->discovery_holdtime); vty_out(vty, " source %pPA", &autorp->mapping_agent_addrsel.run_addr); switch (autorp->mapping_agent_addrsel.cfg_mode) { case CAND_ADDR_LO: vty_out(vty, " (loopback)"); break; case CAND_ADDR_ANY: vty_out(vty, " (any)"); break; case CAND_ADDR_IFACE: vty_out(vty, " (interface %s)", autorp->mapping_agent_addrsel.cfg_ifname); break; case CAND_ADDR_EXPLICIT: vty_out(vty, " (explicit address)"); break; } vty_out(vty, "\n"); if (autorp->mapping_agent_active) { tmp = ttable_dump(adv_table, "\n"); vty_out(vty, "\n"); vty_out(vty, "Advertised RP's (count=%u)\n", (uint32_t)pim_autorp_rp_count(&autorp->advertised_rp_list)); vty_out(vty, "%s\n", tmp); XFREE(MTYPE_TMP_TTABLE, tmp); } else vty_out(vty, " Mapping agent is inactive\n"); } else vty_out(vty, "\n"); } if (cand_table) ttable_del(cand_table); if (adv_table) ttable_del(adv_table); if (disc_table) ttable_del(disc_table); }