mirror of
https://github.com/FRRouting/frr.git
synced 2025-04-30 13:37:17 +02:00

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 <nbahr@atcorp.com>
1148 lines
32 KiB
C
1148 lines
32 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
* pim_autorp.c: PIM AutoRP handling routines
|
|
*
|
|
* Copyright (C) 2024 ATCorp
|
|
* Nathan Bahr
|
|
*/
|
|
|
|
#include <zebra.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
|
|
#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);
|
|
}
|