git: e7b88fdfefcc - main - net/frr10: Enable pimd, install daemons in lib/frr
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 24 Sep 2026 22:55:05 UTC
The branch main has been updated by olivier:
URL: https://cgit.FreeBSD.org/ports/commit/?id=e7b88fdfefccbd47dfc423d1a394db668e9ae6ad
commit e7b88fdfefccbd47dfc423d1a394db668e9ae6ad
Author: Olivier Cochard <olivier@FreeBSD.org>
AuthorDate: 2026-09-24 22:42:47 +0000
Commit: Olivier Cochard <olivier@FreeBSD.org>
CommitDate: 2026-09-24 22:52:01 +0000
net/frr10: Enable pimd, install daemons in lib/frr
Add four patches to make pimd work on FreeBSD (submitted upstream).
Daemons move from ${PREFIX}/sbin to ${PREFIX}/lib/frr, matching the
Linux packages. Only vtysh and mtracebis stay in bin/. This avoids filename
collision with net/openbgpd*, net/pimd, net/quagga, etc.
---
net/frr10/Makefile | 7 +-
net/frr10/files/frr.in | 2 +-
net/frr10/files/patch-pimd_pim__igmp.c | 22 +++
net/frr10/files/patch-pimd_pim__mroute.c | 306 +++++++++++++++++++++++++++++
net/frr10/files/patch-pimd_pim__sock.c | 41 ++++
net/frr10/files/patch-pimd_pim__upstream.c | 71 +++++++
net/frr10/pkg-plist | 105 +++++-----
7 files changed, 502 insertions(+), 52 deletions(-)
diff --git a/net/frr10/Makefile b/net/frr10/Makefile
index c4cf09311529..65e16583a9f1 100644
--- a/net/frr10/Makefile
+++ b/net/frr10/Makefile
@@ -1,6 +1,7 @@
PORTNAME= frr
DISTVERSIONPREFIX= frr-
DISTVERSION= 10.7.1
+PORTREVISION= 1
CATEGORIES= net
.if defined(PYTHONTOOLS)
PKGNAMESUFFIX= 10-pythontools
@@ -45,7 +46,7 @@ USES= autoreconf bison compiler:c++11-lang cpe gmake libtool makeinfo \
BINARY_ALIAS= python=${PYTHON_VERSION}
-CONFLICTS= frr8 frr9 openbgpd openospfd quagga zebra
+CONFLICTS= frr9
CPE_PRODUCT= frrouting
@@ -91,18 +92,18 @@ TCMALLOC_DESC= Use tcmalloc
USERS= frr
GROUPS= frr frrvty
-# PIMD and PBRD compile but doesn't work on FreeBSD
+# PBRD compile but doesn't work on FreeBSD
CONFIGURE_ARGS= --enable-user=frr \
--enable-group=frr \
--enable-vty-group=frrvty \
--enable-vtysh \
--disable-doc-html \
--sysconfdir=${ETCDIR} \
+ --sbindir=${PREFIX}/lib/frr \
--localstatedir=/var/run/frr \
--disable-nhrpd \
--disable-pathd \
--disable-ospfclient \
- --disable-pimd \
--disable-pbrd \
--with-vtysh-pager=cat
diff --git a/net/frr10/files/frr.in b/net/frr10/files/frr.in
index ff443b7d2dc8..c71e4f5bd037 100644
--- a/net/frr10/files/frr.in
+++ b/net/frr10/files/frr.in
@@ -116,7 +116,7 @@ do_cmd() {
ret=0
dmn=$1
if [ -n "${dmn}" ] && [ "${dmn}" != "all" ]; then
- command=%%PREFIX%%/sbin/${dmn}
+ command=%%PREFIX%%/lib/frr/${dmn}
pidfile=/var/run/frr/${dmn}.pid
eval flags=\$\{${dmn}_flags:-\"\"\}
flags="${flags} ${frr_flags}"
diff --git a/net/frr10/files/patch-pimd_pim__igmp.c b/net/frr10/files/patch-pimd_pim__igmp.c
new file mode 100644
index 000000000000..ce85bf5dc5ce
--- /dev/null
+++ b/net/frr10/files/patch-pimd_pim__igmp.c
@@ -0,0 +1,22 @@
+Zero the sockaddr before binding the IGMP socket.
+
+pim_igmp_sock_add() binds its raw IGMP socket with a struct sockaddr_in it never
+zeroes, so sin_zero carries whatever was on the stack. Linux looks at sin_addr
+alone and the garbage is harmless there. FreeBSD resolves the address in
+rip_bind() with ifa_ifwithaddr_fib_check(), which compares every sa_len byte
+through sa_equal() -- sin_zero included -- so the lookup misses a perfectly
+local address and bind() fails with EADDRNOTAVAIL. pimd is then left without an
+IGMP socket on any interface.
+
+Submitted upstream: https://github.com/FRRouting/frr/pull/23455
+
+--- pimd/pim_igmp.c.orig 2026-09-24 18:59:47 UTC
++++ pimd/pim_igmp.c 2026-09-24 18:59:47 UTC
+@@ -1271,6 +1271,7 @@ struct gm_sock *pim_igmp_sock_add(struct list *igmp_sock_list,
+ return NULL;
+ }
+
++ memset(&sin, 0, sizeof(sin));
+ sin.sin_family = AF_INET;
+ sin.sin_addr = ifaddr;
+ sin.sin_port = 0;
diff --git a/net/frr10/files/patch-pimd_pim__mroute.c b/net/frr10/files/patch-pimd_pim__mroute.c
new file mode 100644
index 000000000000..fa9ce02974f3
--- /dev/null
+++ b/net/frr10/files/patch-pimd_pim__mroute.c
@@ -0,0 +1,306 @@
+Four fixes to the multicast routing plumbing on BSD.
+
+1. The mroute socket asked for the ingress ifindex with IP_PKTINFO only, which
+ is Linux and Solaris. pim_mroute_msg() dispatches every IPPROTO_IGMP packet
+ read from that socket to process_igmp_packet(buf, ifindex), so on BSD ifindex
+ stayed -1, if_lookup_by_index(-1) returned NULL, and every membership report
+ was dropped without a log line. Use setsockopt_ifindex(), which picks the
+ right option per platform.
+
+2. pimreg is a pseudo interface with no address, which is fine on Linux where
+ the vif is added by ifindex. BSD adds a vif by local address and add_vif()
+ rejects INADDR_ANY before it looks at VIFF_REGISTER, so the register vif was
+ never created and no PIM Register was ever built. Lend it the address of
+ another PIM interface, as net/pimd's update_reg_vif() does.
+
+3. SIOCGETSGCNT is gated on PRIV_NETINET_MROUTE in X_mrt_ioctl() and pimd has
+ dropped to its unprivileged user, so the (S,G) counters stayed at zero -- the
+ counters the keepalive timer and the SPT switchover decision read.
+
+4. MRT_DEL_MFC was not wrapped in frr_with_privs while the MRT_ADD_MFC that
+ installs the entry is, so every mroute removal failed with EPERM and left the
+ entry in the kernel with nothing in the daemon referring to it.
+
+Plus: a last hop router has to build (S,G) state itself here. Linux resolves an
+(S,G) miss against the (*,G) entry in ipmr_cache_find_any() and forwards the
+packet; BSD's mfc_find() matches the source exactly and has no wildcard mfc, so
+the kernel raises a NOCACHE upcall for every new source instead and
+pim_mroute_nocache_forward_existing() returned early for want of an (S,G)
+upstream -- leaving an mfc entry with an empty outgoing list that silenced
+further upcalls and black-holed the traffic.
+
+Submitted upstream: https://github.com/FRRouting/frr/pull/23455
+
+--- pimd/pim_mroute.c.orig 2026-09-24 18:59:47 UTC
++++ pimd/pim_mroute.c 2026-09-24 18:59:47 UTC
+@@ -113,33 +113,18 @@ int pim_mroute_set(struct pim_instance *pim, int enable)
+ }
+ }
+
+-#if defined(HAVE_IP_PKTINFO)
+- if (enable) {
+- /* Linux and Solaris IP_PKTINFO */
+- data = 1;
+- if (setsockopt(pim->mroute_socket, PIM_IPPROTO, IP_PKTINFO,
+- &data, data_len)) {
+- zlog_warn(
+- "Could not set IP_PKTINFO on socket fd=%d: errno=%d: %s",
+- pim->mroute_socket, errno,
+- safe_strerror(errno));
+- }
+- }
+-#endif
+-
+-#if PIM_IPV == 6
+- if (enable) {
+- /* Linux and Solaris IPV6_PKTINFO */
+- data = 1;
+- if (setsockopt(pim->mroute_socket, PIM_IPPROTO,
+- IPV6_RECVPKTINFO, &data, data_len)) {
+- zlog_warn(
+- "Could not set IPV6_RECVPKTINFO on socket fd=%d: errno=%d: %s",
+- pim->mroute_socket, errno,
+- safe_strerror(errno));
+- }
+- }
+-#endif
++ /*
++ * pim_mroute_msg() hands every IPPROTO_IGMP packet read from this
++ * socket to process_igmp_packet(), which needs the ingress ifindex to
++ * find the interface the report came in on; without it
++ * if_lookup_by_index() fails and every IGMP report is dropped
++ * silently. setsockopt_ifindex() asks for it with whatever this
++ * platform provides -- IP_PKTINFO on Linux and Solaris, IP_RECVIF on
++ * BSD, IPV6_RECVPKTINFO for v6.
++ */
++ if (enable && setsockopt_ifindex(PIM_AF, pim->mroute_socket, 1) < 0)
++ zlog_warn("Could not request the ingress ifindex on socket fd=%d",
++ pim->mroute_socket);
+ setsockopt_so_recvbuf(pim->mroute_socket, 1024 * 1024 * 8);
+
+ if (set_nonblocking(pim->mroute_socket) < 0) {
+@@ -194,10 +179,51 @@ static void pim_mroute_nocache_forward_existing(struct interface *ifp, pim_sgadd
+ struct pim_nexthop rpf_nh;
+ struct prefix grp;
+ struct pim_rpf old;
++ bool on_rpt = false;
+
+ up = pim_upstream_find(pim, sg);
+- if (!up)
+- return;
++ if (!up) {
++ /*
++ * No (S,G) state yet. On Linux this upcall would never have
++ * been raised: ipmr_cache_find_any() resolves the lookup
++ * against the (*,G) entry and the kernel forwards the packet
++ * itself. BSD's mfc_find() matches the source exactly and has
++ * no wildcard mfc, so a last hop router whose receivers joined
++ * (*,G) is asked about every new source and has to build the
++ * (S,G) itself, or the traffic stops one hop short of the
++ * receiver.
++ *
++ * Only do so when the (*,G) it would inherit from exists --
++ * otherwise this is a source nobody here asked for. The
++ * creation is the same one the WHOLEPKT path uses for an LHR;
++ * the rest of this function resolves the iif and inherits the
++ * olist, and returns without installing anything if there turn
++ * out to be no receivers.
++ */
++ pim_sgaddr star_g = *sg;
++
++ star_g.src = PIMADDR_ANY;
++ if (!pim_upstream_find(pim, &star_g))
++ return;
++
++ up = pim_upstream_add(pim, sg, ifp, PIM_UPSTREAM_FLAG_MASK_SRC_LHR, __func__, NULL);
++ if (!up)
++ return;
++
++ /*
++ * The keepalive is this entry's expiry: when the source stops,
++ * it fires and pim_upstream_keep_alive_timer_proc() releases
++ * the SRC_LHR reference, taking the mfc with it. It no longer
++ * drags the router onto the source tree either -- whether that
++ * switch may happen is decided by spt-switchover, in
++ * pim_upstream_evaluate_join_desired().
++ */
++ pim_upstream_keep_alive_timer_start(up, pim->keep_alive_time);
++
++ if (PIM_DEBUG_MROUTE)
++ zlog_debug("%s: %pSG NOCACHE on %s, creating (S,G) from (*,G) on LHR",
++ __func__, sg, ifp->name);
++ }
+
+ memset(&rpf_nh, 0, sizeof(rpf_nh));
+ pim_addr_to_prefix(&grp, sg->grp);
+@@ -216,14 +242,31 @@ static void pim_mroute_nocache_forward_existing(struct interface *ifp, pim_sgadd
+ }
+
+ if (!rpf_nh.interface || rpf_nh.interface->ifindex != ifp->ifindex) {
+- if (PIM_DEBUG_MROUTE_DETAIL)
+- zlog_debug("%s: %pSG NOCACHE on %s, RPF interface is %s", __func__, sg,
+- ifp->name, rpf_nh.interface ? rpf_nh.interface->name : "(none)");
+- return;
++ /*
++ * Not the RPF interface towards the source -- which on a
++ * shared tree is the ordinary case, not an error: the packet
++ * comes from the RP, and it is the (*,G) this (S,G) inherits
++ * from that knows the interface it arrives on. Forward it
++ * along the RPT instead of dropping it; Linux's wildcard mfc
++ * does exactly that without involving the daemon at all.
++ *
++ * A packet on neither interface is genuinely unexpected, and
++ * still returns.
++ */
++ if (!up->parent || !up->parent->rpf.source_nexthop.interface ||
++ up->parent->rpf.source_nexthop.interface->ifindex != ifp->ifindex) {
++ if (PIM_DEBUG_MROUTE_DETAIL)
++ zlog_debug("%s: %pSG NOCACHE on %s, RPF interface is %s", __func__,
++ sg, ifp->name,
++ rpf_nh.interface ? rpf_nh.interface->name : "(none)");
++ return;
++ }
++ on_rpt = true;
+ }
+
+- if (up->rpf.source_nexthop.interface != rpf_nh.interface ||
+- pim_addr_cmp(up->rpf.source_nexthop.mrib_nexthop_addr, rpf_nh.mrib_nexthop_addr)) {
++ if (!on_rpt &&
++ (up->rpf.source_nexthop.interface != rpf_nh.interface ||
++ pim_addr_cmp(up->rpf.source_nexthop.mrib_nexthop_addr, rpf_nh.mrib_nexthop_addr))) {
+ enum pim_rpf_result rpf_result;
+
+ memset(&old, 0, sizeof(old));
+@@ -233,6 +276,16 @@ static void pim_mroute_nocache_forward_existing(struct interface *ifp, pim_sgadd
+ pim_zebra_upstream_rpf_changed(pim, up, &old);
+ }
+
++ /*
++ * An (S,G) forwarding off the shared tree takes its iif from the
++ * parent rather than from the route to the source, and USE_RPT is
++ * what pim_upstream_get_mroute_iif() reads to pick it. Without this
++ * the entry would be installed pointing at the interface the traffic
++ * is not arriving on.
++ */
++ if (on_rpt)
++ pim_upstream_update_use_rpt(up, false /*update_mroute*/);
++
+ pim_upstream_inherited_olist_decide(pim, up);
+ if (pim_upstream_empty_inherited_olist(up)) {
+ if (PIM_DEBUG_MROUTE_DETAIL)
+@@ -241,10 +294,21 @@ static void pim_mroute_nocache_forward_existing(struct interface *ifp, pim_sgadd
+ return;
+ }
+
+- if (up->sptbit != PIM_UPSTREAM_SPTBIT_TRUE)
++ /* Only the source tree sets the SPT bit; on the RPT we have not
++ * switched trees and must not claim to have.
++ */
++ if (!on_rpt && up->sptbit != PIM_UPSTREAM_SPTBIT_TRUE)
+ pim_upstream_set_sptbit(up, ifp);
+
+- PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
++ /*
++ * SRC_STREAM says this entry exists because traffic is flowing on the
++ * source tree, and the keepalive expiry releases the stream reference
++ * that goes with it. An (S,G) forwarding off the shared tree is not
++ * that: its reference is SRC_LHR, and it has to expire through that
++ * branch instead.
++ */
++ if (!on_rpt)
++ PIM_UPSTREAM_FLAG_SET_SRC_STREAM(up->flags);
+ up->channel_oil->cc.pktcnt++;
+
+ pim_upstream_update_join_desired(pim, up);
+@@ -1618,6 +1682,38 @@ int pim_mroute_add_vif(struct interface *ifp, pim_addr ifaddr,
+ #ifdef VIFF_USE_IFINDEX
+ vc.vc_lcl_ifindex = ifp->ifindex;
+ #else
++ if (ifaddr.s_addr == INADDR_ANY && (flags & VIFF_REGISTER)) {
++ /*
++ * pimreg is a pseudo interface with no address of its own
++ * (pim_if_create_pimreg()), which on Linux is fine: the vif is
++ * added by ifindex. BSD adds a vif by local address, and its
++ * add_vif() rejects INADDR_ANY before it ever looks at
++ * VIFF_REGISTER -- even though the same function documents
++ * that a register vif "does not really need a valid local
++ * interface". Without an address here the register vif is
++ * never created, so the first hop router's mfc entry points at
++ * a vif that does not exist, no PIM Register is ever built,
++ * and the RP never learns of a source behind it.
++ *
++ * Lend it the address of another PIM interface, and fall back
++ * to a loopback address when none has one yet, since only
++ * non-nullness is actually required.
++ */
++ struct interface *tmp_ifp;
++
++ FOR_ALL_INTERFACES (pim_ifp->pim->vrf, tmp_ifp) {
++ struct pim_interface *tmp_pim_ifp = tmp_ifp->info;
++
++ if (!tmp_pim_ifp || tmp_ifp == ifp)
++ continue;
++ if (pim_addr_is_any(tmp_pim_ifp->primary_address))
++ continue;
++ ifaddr = tmp_pim_ifp->primary_address;
++ break;
++ }
++ if (ifaddr.s_addr == INADDR_ANY)
++ ifaddr.s_addr = htonl(INADDR_LOOPBACK + 1);
++ }
+ if (ifaddr.s_addr == INADDR_ANY) {
+ zlog_warn(
+ "%s: unnumbered interfaces are not supported on this platform",
+@@ -1973,8 +2069,18 @@ int pim_mroute_del(struct channel_oil *c_oil, const char *name)
+ return -2;
+ }
+
+- err = setsockopt(pim->mroute_socket, PIM_IPPROTO, MRT_DEL_MFC,
+- &c_oil->oil, sizeof(c_oil->oil));
++ /*
++ * Privileged, exactly as the MRT_ADD_MFC that put the entry there is:
++ * a kernel that checks (FreeBSD gates the mroute setsockopts on
++ * PRIV_NETINET_MROUTE) otherwise answers EPERM, and pimd goes on to
++ * free its own state, leaving the mfc installed with nothing left to
++ * remove it.
++ */
++ frr_with_privs (&pimd_privs) {
++ err = setsockopt(pim->mroute_socket, PIM_IPPROTO, MRT_DEL_MFC, &c_oil->oil,
++ sizeof(c_oil->oil));
++ }
++
+ if (err) {
+ if (PIM_DEBUG_MROUTE)
+ zlog_warn(
+@@ -2001,6 +2107,7 @@ void pim_mroute_update_counters(struct channel_oil *c_oil)
+ {
+ struct pim_instance *pim = c_oil->pim;
+ pim_sioc_sg_req sgreq;
++ int err;
+
+ c_oil->cc.oldpktcnt = c_oil->cc.pktcnt;
+ c_oil->cc.oldbytecnt = c_oil->cc.bytecnt;
+@@ -2031,7 +2138,22 @@ void pim_mroute_update_counters(struct channel_oil *c_oil)
+ sgreq.src = c_oil->oil.mf6cc_origin;
+ sgreq.grp = c_oil->oil.mf6cc_mcastgrp;
+ #endif
+- if (ioctl(pim->mroute_socket, PIM_SIOCGETSGCNT, &sgreq)) {
++#ifdef GNU_LINUX
++ err = ioctl(pim->mroute_socket, PIM_SIOCGETSGCNT, &sgreq);
++#else
++ /*
++ * BSD gates this ioctl on PRIV_NETINET_MROUTE (X_mrt_ioctl()) and pimd
++ * has long since dropped to its unprivileged user, so every call
++ * returned EPERM and the (S,G) counters stayed at zero -- which is
++ * what the keepalive timer and the SPT switchover decision are read
++ * from.
++ */
++ frr_with_privs (&pimd_privs) {
++ err = ioctl(pim->mroute_socket, PIM_SIOCGETSGCNT, &sgreq);
++ }
++#endif
++
++ if (err) {
+ pim_sgaddr sg;
+
+ sg.src = *oil_origin(c_oil);
diff --git a/net/frr10/files/patch-pimd_pim__sock.c b/net/frr10/files/patch-pimd_pim__sock.c
new file mode 100644
index 000000000000..2cab9150f31e
--- /dev/null
+++ b/net/frr10/files/patch-pimd_pim__sock.c
@@ -0,0 +1,41 @@
+Ask for IP_HDRINCL on the register socket: only Linux implies it.
+
+pim_msg_send() builds the outer IP header for every message it puts on
+pim->reg_sock, which pim_reg_sock() creates with SOCK_RAW/IPPROTO_RAW. Linux
+implies IP_HDRINCL for IPPROTO_RAW, so that header is what goes on the wire.
+BSD does not: rip_output() prepends a header of its own with ip_p taken from the
+socket's protocol, so every PIM Register left FreeBSD as IP protocol 255 with
+pimd's own header as payload, and the RP answered with an ICMP "protocol 255
+unreachable" rather than learning the source.
+
+Submitted upstream: https://github.com/FRRouting/frr/pull/23455
+
+--- pimd/pim_sock.c.orig 2026-09-24 18:59:47 UTC
++++ pimd/pim_sock.c 2026-09-24 18:59:47 UTC
+@@ -198,6 +198,26 @@ int pim_reg_sock(void)
+ return PIM_SOCK_ERR_SOCKET;
+ }
+
++#if PIM_IPV == 4
++ /*
++ * pim_msg_send() builds the outer IP header itself for everything it
++ * puts on this socket. Linux implies IP_HDRINCL for an IPPROTO_RAW
++ * socket, so that header goes on the wire as written; BSD does not,
++ * and without the option the kernel prepends a second header of its
++ * own carrying ip_p = IPPROTO_RAW. A Register then leaves as IP
++ * protocol 255 with FRR's header as payload, and the RP answers
++ * "protocol 255 unreachable" instead of learning the source.
++ * Setting it explicitly is a no-op on Linux and the fix on BSD.
++ */
++ {
++ int hincl = 1;
++
++ if (setsockopt(fd, IPPROTO_IP, IP_HDRINCL, &hincl, sizeof(hincl)))
++ zlog_warn("Could not set IP_HDRINCL on socket fd=%d: errno=%d: %s", fd,
++ errno, safe_strerror(errno));
++ }
++#endif
++
+ if (sockopt_reuseaddr(fd)) {
+ close(fd);
+ return PIM_SOCK_ERR_REUSE;
diff --git a/net/frr10/files/patch-pimd_pim__upstream.c b/net/frr10/files/patch-pimd_pim__upstream.c
new file mode 100644
index 000000000000..6f3bec32559d
--- /dev/null
+++ b/net/frr10/files/patch-pimd_pim__upstream.c
@@ -0,0 +1,71 @@
+Honour spt-switchover when a source is learned off the shared tree.
+
+spt-switchover infinity-and-beyond is enforced only by withholding the register
+vif from the (*,G). That is enough while the register punt is the only way a
+last hop router can learn a source, and it is not:
+pim_upstream_evaluate_join_desired() joins an (S,G) as soon as its inherited
+olist is non-empty and a keepalive is running, whichever tree the traffic
+arrived on. A router that learns a source another way therefore leaves the
+shared tree behind the operator's back -- reproducible here, where the last hop
+router holds (S,G) state so the kernel can forward at all.
+
+Submitted upstream: https://github.com/FRRouting/frr/pull/23455
+
+--- pimd/pim_upstream.c.orig 2026-09-24 18:59:47 UTC
++++ pimd/pim_upstream.c 2026-09-24 18:59:47 UTC
+@@ -1539,6 +1539,33 @@ static inline bool pim_upstream_is_msdp_peer_sa(struct pim_upstream *up)
+ * AND inherited_olist(S,G) != NULL ) )
+ * }
+ */
++/*
++ * Is this router allowed to leave the shared tree for this group?
++ *
++ * spt-switchover infinity-and-beyond says never, optionally narrowed to the
++ * groups a prefix list does not deny. Until now the setting was enforced
++ * only by withholding the register vif from the (*,G), which is enough as
++ * long as that punt is the only way a last hop router can learn a source --
++ * it is not, so check it where the join decision is actually made.
++ */
++static bool pim_upstream_spt_switch_allowed(struct pim_instance *pim, pim_addr grp)
++{
++ struct prefix_list *plist;
++ struct prefix g;
++
++ if (pim->spt.switchover != PIM_SPT_INFINITY)
++ return true;
++
++ if (!pim->spt.plist)
++ return false;
++
++ plist = prefix_list_lookup(PIM_AFI, pim->spt.plist);
++ pim_addr_to_prefix(&g, grp);
++
++ /* A group the list denies is exempt from the never-switch policy. */
++ return prefix_list_apply_ext(plist, NULL, &g, true) == PREFIX_DENY;
++}
++
+ bool pim_upstream_evaluate_join_desired(struct pim_instance *pim,
+ struct pim_upstream *up)
+ {
+@@ -1556,9 +1583,19 @@ bool pim_upstream_evaluate_join_desired(struct pim_instance *pim,
+ return true;
+ empty_inh_oil = pim_upstream_empty_inherited_olist(up);
+ if (!empty_inh_oil &&
+- (pim_upstream_is_kat_running(up) ||
+- pim_upstream_is_msdp_peer_sa(up)))
++ (pim_upstream_is_kat_running(up) || pim_upstream_is_msdp_peer_sa(up))) {
++ /*
++ * Joining an (S,G) whose traffic is arriving on the shared
++ * tree is the last-hop switch to the source tree. Where the
++ * operator has turned that off, stay on the shared tree
++ * however much traffic shows up.
++ */
++ if (PIM_UPSTREAM_FLAG_TEST_USE_RPT(up->flags) &&
++ !pim_upstream_spt_switch_allowed(pim, up->sg.grp))
++ return false;
++
+ return true;
++ }
+
+ return false;
+ }
diff --git a/net/frr10/pkg-plist b/net/frr10/pkg-plist
index dde1e10226dc..744eeab9f33f 100644
--- a/net/frr10/pkg-plist
+++ b/net/frr10/pkg-plist
@@ -1,4 +1,6 @@
+bin/mtracebis
bin/vtysh
+include/frr/bfdd/bfddp_packet.h
include/frr/eigrpd/eigrp_dump.h
include/frr/eigrpd/eigrp_topology.h
include/frr/eigrpd/eigrpd.h
@@ -11,7 +13,6 @@ include/frr/lib/atomlist.h
include/frr/lib/atomptr.h
include/frr/lib/base64.h
include/frr/lib/bfd.h
-include/frr/bfdd/bfddp_packet.h
include/frr/lib/bitfield.h
include/frr/lib/buffer.h
include/frr/lib/checksum.h
@@ -53,9 +54,9 @@ include/frr/lib/jhash.h
include/frr/lib/json.h
include/frr/lib/keychain.h
include/frr/lib/ldp_sync.h
-include/frr/lib/libagentx.h
include/frr/lib/lib_errors.h
include/frr/lib/lib_vty.h
+include/frr/lib/libagentx.h
include/frr/lib/libfrr.h
include/frr/lib/libfrr_trace.h
include/frr/lib/libospf.h
@@ -85,18 +86,6 @@ include/frr/lib/northbound_db.h
include/frr/lib/ns.h
include/frr/lib/openbsd-queue.h
include/frr/lib/openbsd-tree.h
-include/frr/lib/vlan.h
-include/frr/lib/workqueue.h
-include/frr/ospfd/ospf_api.h
-include/frr/ospfd/ospf_asbr.h
-include/frr/ospfd/ospf_dump.h
-include/frr/ospfd/ospf_dump_api.h
-include/frr/ospfd/ospf_ism.h
-include/frr/ospfd/ospf_lsa.h
-include/frr/ospfd/ospf_lsdb.h
-include/frr/ospfd/ospf_nsm.h
-include/frr/ospfd/ospf_opaque.h
-include/frr/ospfd/ospfd.h
include/frr/lib/pbr.h
include/frr/lib/plist.h
include/frr/lib/prefix.h
@@ -136,11 +125,13 @@ include/frr/lib/typerb.h
include/frr/lib/typesafe.h
include/frr/lib/vector.h
include/frr/lib/version.h
+include/frr/lib/vlan.h
include/frr/lib/vrf.h
include/frr/lib/vrf_int.h
include/frr/lib/vty.h
include/frr/lib/vxlan.h
include/frr/lib/wheel.h
+include/frr/lib/workqueue.h
include/frr/lib/xref.h
include/frr/lib/yang.h
include/frr/lib/yang_translator.h
@@ -152,6 +143,16 @@ include/frr/lib/zlog_5424.h
include/frr/lib/zlog_live.h
include/frr/lib/zlog_recirculate.h
include/frr/lib/zlog_targets.h
+include/frr/ospfd/ospf_api.h
+include/frr/ospfd/ospf_asbr.h
+include/frr/ospfd/ospf_dump.h
+include/frr/ospfd/ospf_dump_api.h
+include/frr/ospfd/ospf_ism.h
+include/frr/ospfd/ospf_lsa.h
+include/frr/ospfd/ospf_lsdb.h
+include/frr/ospfd/ospf_nsm.h
+include/frr/ospfd/ospf_opaque.h
+include/frr/ospfd/ospfd.h
include/frr/zebra/connected.h
include/frr/zebra/debug.h
include/frr/zebra/ge_netlink.h
@@ -216,8 +217,34 @@ include/frr/zebra/zebra_vxlan.h
include/frr/zebra/zebra_vxlan_if.h
include/frr/zebra/zebra_vxlan_private.h
include/frr/zebra/zserv.h
-share/yang/frr-logging.yang
-share/yang/ietf-syslog-types.yang
+lib/frr/babeld
+lib/frr/bfdd
+lib/frr/bgpd
+lib/frr/eigrpd
+lib/frr/fabricd
+lib/frr/fpm_listener
+lib/frr/isisd
+%%LDPD%%lib/frr/ldpd
+lib/frr/mgmtd
+%%SNMP%%lib/frr/modules/bgpd_bmp.so
+%%RPKI%%lib/frr/modules/bgpd_rpki.so
+%%SNMP%%lib/frr/modules/bgpd_snmp.so
+%%SNMP%%lib/frr/modules/isisd_snmp.so
+%%SNMP%%%%LDPD%%lib/frr/modules/ldpd_snmp.so
+%%SNMP%%lib/frr/modules/ospf6d_snmp.so
+%%SNMP%%lib/frr/modules/ospfd_snmp.so
+%%SNMP%%lib/frr/modules/ripd_snmp.so
+%%FPM%%lib/frr/modules/zebra_fpm.so
+%%SNMP%%lib/frr/modules/zebra_snmp.so
+lib/frr/ospf6d
+lib/frr/ospfd
+lib/frr/pimd
+lib/frr/ripd
+lib/frr/ripngd
+%%SHARPD%%lib/frr/sharpd
+lib/frr/staticd
+lib/frr/watchfrr
+lib/frr/zebra
lib/libfrr.a
lib/libfrr.so
lib/libfrr.so.0
@@ -246,49 +273,24 @@ lib/libmlag_pb.a
lib/libmlag_pb.so
lib/libmlag_pb.so.0
lib/libmlag_pb.so.0.0.0
-%%SNMP%%lib/frr/modules/bgpd_bmp.so
-%%RPKI%%lib/frr/modules/bgpd_rpki.so
-%%SNMP%%lib/frr/modules/bgpd_snmp.so
-%%SNMP%%lib/frr/modules/isisd_snmp.so
-%%SNMP%%%%LDPD%%lib/frr/modules/ldpd_snmp.so
-%%SNMP%%lib/frr/modules/ospf6d_snmp.so
-%%SNMP%%lib/frr/modules/ospfd_snmp.so
-%%SNMP%%lib/frr/modules/ripd_snmp.so
-%%FPM%%lib/frr/modules/zebra_fpm.so
-%%SNMP%%lib/frr/modules/zebra_snmp.so
+sbin/frr-reload
%%MANPAGES%%share/man/man1/frr.1.gz
+%%MANPAGES%%share/man/man1/vtysh.1.gz
%%MANPAGES%%share/man/man8/frr-bfdd.8.gz
+%%MANPAGES%%share/man/man8/frr-bgpd.8.gz
%%MANPAGES%%share/man/man8/frr-eigrpd.8.gz
%%MANPAGES%%share/man/man8/frr-isisd.8.gz
-%%MANPAGES%%share/man/man8/frr-staticd.8.gz
-%%MANPAGES%%share/man/man8/frr-watchfrr.8.gz
-%%MANPAGES%%share/man/man1/vtysh.1.gz
-%%MANPAGES%%share/man/man8/frr-bgpd.8.gz
%%MANPAGES%%%%LDPD%%share/man/man8/frr-ldpd.8.gz
%%MANPAGES%%share/man/man8/frr-ospf6d.8.gz
%%MANPAGES%%share/man/man8/frr-ospfd.8.gz
+%%MANPAGES%%share/man/man8/frr-pimd.8.gz
%%MANPAGES%%share/man/man8/frr-ripd.8.gz
%%MANPAGES%%share/man/man8/frr-ripngd.8.gz
%%MANPAGES%%%%SHARPD%%share/man/man8/frr-sharpd.8.gz
+%%MANPAGES%%share/man/man8/frr-staticd.8.gz
+%%MANPAGES%%share/man/man8/frr-watchfrr.8.gz
%%MANPAGES%%share/man/man8/frr-zebra.8.gz
-sbin/babeld
-sbin/bfdd
-sbin/bgpd
-sbin/eigrpd
-sbin/fabricd
-sbin/fpm_listener
-sbin/frr-reload
-%%LDPD%%sbin/ldpd
-sbin/isisd
-sbin/mgmtd
-sbin/ospf6d
-sbin/ospfd
-sbin/ripd
-sbin/ripngd
-%%SHARPD%%sbin/sharpd
-sbin/staticd
-sbin/watchfrr
-sbin/zebra
+%%MANPAGES%%share/man/man8/mtracebis.8.gz
share/yang/frr-affinity-map.yang
share/yang/frr-backend.yang
share/yang/frr-bfdd.yang
@@ -307,15 +309,21 @@ share/yang/frr-deviations-bgp-datacenter.yang
share/yang/frr-deviations-ietf-key-chain.yang
share/yang/frr-eigrpd.yang
share/yang/frr-filter.yang
+share/yang/frr-gmp.yang
share/yang/frr-host.yang
share/yang/frr-if-rmap.yang
share/yang/frr-interface.yang
share/yang/frr-isisd.yang
+share/yang/frr-logging.yang
share/yang/frr-module-translator.yang
share/yang/frr-nexthop.yang
share/yang/frr-ospf-route-map.yang
share/yang/frr-ospf6-route-map.yang
share/yang/frr-ospfd.yang
+share/yang/frr-pim-candidate.yang
+share/yang/frr-pim-route-map.yang
+share/yang/frr-pim-rp.yang
+share/yang/frr-pim.yang
share/yang/frr-ripd.yang
share/yang/frr-ripngd.yang
share/yang/frr-route-map.yang
@@ -335,4 +343,5 @@ share/yang/ietf-netconf-with-defaults.yang
share/yang/ietf-netconf.yang
share/yang/ietf-routing-types.yang
share/yang/ietf-srv6-types.yang
+share/yang/ietf-syslog-types.yang
@dir(frr,frr,755) /var/run/frr