Multicast

PIM Sparse Mode: RPs, Shared Trees, and the SPT Switchover

PIM sparse mode - feature image with real mroute state showing the SPT switchover JT flags
In: Multicast, Labs

PIM sparse mode is where multicast stops being theory and becomes state machines. It is the protocol that turns "a host joined a group" into an actual delivery tree across your network, and it does it with a three-act structure: receivers join a shared tree toward a rendezvous point, sources register to that same RP, and then, the moment traffic flows, the edge quietly rebuilds everything onto shortest-path trees. This article walks the whole lifecycle on IOS XE with live captures at every stage, including the before and after of the SPT switchover. Part of the IP multicast guide.

The Problem PIM-SM Solves

In any-source multicast, receivers do not know who the sources are; they just want group G. Sources do not know who the receivers are; they just send. Someone has to introduce them. Dense-mode protocols solved this by flooding traffic everywhere and pruning back, which scales exactly as badly as it sounds. Sparse mode inverts the model: nothing is forwarded anywhere until someone explicitly asks, and the rendezvous point is the agreed meeting place where asks and offers converge.

PIM is "protocol independent" because it builds no topology of its own. Every forwarding decision borrows the unicast routing table through the RPF check (the subject of its own article). In this lab the underlay is OSPF area 0 across four IOL-XE routers, with R2 as the RP and a real Debian VM as the receiver behind R1.

Baseline: Neighbors and the RP

PIM speaks hellos on 224.0.0.13 on every interface you enable it on. Adjacencies and the RP agreement are the two things to verify before expecting any tree to form:

R2# show ip pim neighbor
Neighbor          Interface                Uptime/Expires    Ver   DR
10.0.12.1         Ethernet0/0              00:02:06/00:01:36 v2    1 / S P G
10.0.23.2         Ethernet0/1              00:02:04/00:01:39 v2    1 / DR S P G
10.0.24.2         Ethernet0/2              00:01:59/00:01:42 v2    1 / DR S P G

R1# show ip pim rp mapping
Group(s): 224.0.0.0/4, Static
    RP: 2.2.2.2 (?)

Every router in the domain must resolve the same RP for a given group, whether learned statically (as here), via BSR, or Auto-RP. Mismatched RP knowledge is failure pattern number three in troubleshooting multicast.

Act One: A Receiver Joins the Shared Tree

The Debian VM joins 239.1.1.10 with IGMP. R1, as last-hop router, creates a (*,G) entry and sends a PIM (*,G) join hop by hop toward the RP (this is why sparse mode is called explicit join). Real state, seconds after the join, with no source anywhere yet:

R1# show ip mroute 239.1.1.10
(*, 239.1.1.10), 00:00:22/00:02:39, RP 2.2.2.2, flags: SC
  Incoming interface: Ethernet0/1, RPF nbr 10.0.12.2
  Outgoing interface list:
    Ethernet0/0, Forward/Sparse, 00:00:22/00:02:39

Read the flags: S is sparse, C means a directly connected member (the IGMP join on Ethernet0/0). The incoming interface points at the RP because the shared tree is rooted there. The RP itself now shows the tree hanging off it:

R2# show ip mroute 239.1.1.10
(*, 239.1.1.10), 00:00:29/00:03:00, RP 2.2.2.2, flags: S
  Incoming interface: Null, RPF nbr 0.0.0.0
  Outgoing interface list:
    Ethernet0/0, Forward/Sparse, 00:00:29/00:03:00

Incoming interface Null: the RP is the root, so nothing is upstream of it on the shared tree. The OIL points back toward R1 and the waiting receiver.

Act Two: A Source Registers

Now the server behind R3 starts sending to 239.1.1.10. R3, as the source's DR, cannot just forward: no one downstream has asked it for anything. Instead it encapsulates the first packets in unicast PIM register messages and tunnels them straight to the RP. The RP decapsulates, forwards down the shared tree, and, because it now knows the source address, sends an (S,G) join back toward the source to pull traffic natively. Once native traffic arrives, the RP sends register-stop and the tunnel closes. The aftermath is visible on R3:

R3# show ip mroute 239.1.1.10
(*, 239.1.1.10), 00:02:01/stopped, RP 2.2.2.2, flags: SPF
  Incoming interface: Ethernet0/0, RPF nbr 10.0.23.1
  Outgoing interface list: Null

(10.0.30.10, 239.1.1.10), 00:02:01/00:03:29, flags: FT
  Incoming interface: Ethernet0/1, RPF nbr 0.0.0.0
  Outgoing interface list:
    Ethernet0/0, Forward/Sparse, 00:02:01/00:03:25

The F flag is the register machinery, T confirms native (S,G) forwarding toward the RP. RPF neighbor 0.0.0.0 on the (S,G) means the source is directly connected. Meanwhile the RP holds both trees and stitches them together:

R2# show ip mroute 239.1.1.10
(*, 239.1.1.10), 00:02:41/00:02:45, RP 2.2.2.2, flags: S
  Outgoing interface list:
    Ethernet0/0, Forward/Sparse, 00:02:41/00:02:45

(10.0.30.10, 239.1.1.10), 00:01:53/00:01:07, flags: TA
  Incoming interface: Ethernet0/1, RPF nbr 10.0.23.2
  Outgoing interface list:
    Ethernet0/0, Forward/Sparse, 00:01:53/00:02:45

(The A flag marks this (S,G) as a candidate for MSDP advertisement, which becomes relevant in interdomain multicast.)

Act Three: The SPT Switchover

Traffic is now flowing source to receiver, but along a detour: through the RP. If the RP is not on the shortest path between source and receiver, that detour costs latency and bandwidth forever. So PIM-SM's last trick: the moment the last-hop router receives the first packet down the shared tree, it learns the source address from the packet itself, sends an (S,G) join along the shortest unicast path to the source, and prunes the source off the shared tree once native SPT traffic arrives.

On IOS XE this is governed by ip pim spt-threshold, default 0: switch over on the first packet. For this capture the lab was first pinned to the shared tree with ip pim spt-threshold infinity, then released. Before, on R1:

R1# show ip mroute 239.1.1.10      ! spt-threshold infinity
(*, 239.1.1.10), 00:03:04/00:02:24, RP 2.2.2.2, flags: SC
  Incoming interface: Ethernet0/1, RPF nbr 10.0.12.2
  Outgoing interface list:
    Ethernet0/0, Forward/Sparse, 00:03:04/00:02:24

One entry, no (S,G), traffic riding the RP tree indefinitely. Then no ip pim spt-threshold infinity, and within one packet:

R1# show ip mroute 239.1.1.10      ! default spt-threshold 0
(*, 239.1.1.10), 00:03:34/stopped, RP 2.2.2.2, flags: SJC
  Incoming interface: Ethernet0/1, RPF nbr 10.0.12.2

(10.0.30.10, 239.1.1.10), 00:00:05/00:02:54, flags: JT
  Incoming interface: Ethernet0/1, RPF nbr 10.0.12.2
  Outgoing interface list:
    Ethernet0/0, Forward/Sparse, 00:00:05/00:02:54

The J flag on both entries marks the join-SPT decision; T on the (S,G) confirms packets are arriving on the source tree. In this topology the shared tree and SPT happen to share links (R2 sits on the shortest path), so the win is invisible in the path; in a real network with an off-path RP, this is where the detour disappears. The router even accounts for the transition, showing exactly how many packets rode each tree:

R1# show ip mroute count | include 239.1.1.10|tree|Source:
Group: 239.1.1.10, Source count: 1, Packets forwarded: 336, Packets received: 336
  RP-tree: Forwarding: 323/0/60/0, Other: 323/0/0
  Source: 10.0.30.10/32, Forwarding: 13/1/61/0, Other: 13/0/0

323 packets on the shared tree, 13 on the SPT and counting. That pair of counters is the SPT switchover, quantified.

Inside the Register Tunnel

The register process deserves a closer look because two of its failure modes are ENCOR favorites. When R3's first multicast packet arrives from the source, R3 builds a unicast IP packet addressed to the RP, sets the protocol to PIM, and stuffs the entire original multicast packet inside: data-plane traffic hidden inside control-plane messaging. On IOS XE you can watch the tunnel interfaces get created the moment PIM learns an RP (show interface tunnel0 reveals a "Pim Register Tun" toward 2.2.2.2; the RP grows a decap tunnel to match). Two registers actually exist: data registers carrying payload, and null registers, header-only keepalives the first hop keeps sending about once a minute so the RP remembers the source while suppression is in effect.

The failure modes: first, the RP must have a valid RPF path back to the source, or it cannot send the (S,G) join that lets it request native traffic; the register tunnel keeps working, the join never arrives, and the source stays stuck in register forever, burning first-hop CPU (the F flag never clearing is your clue). Second, registration is a unicast transaction with the RP address, so anything that breaks unicast to the RP (an ACL, a missing loopback advertisement) breaks source registration while leaving receiver-side shared trees looking perfectly healthy. Asymmetric symptoms, one cause.

Who Sends What: DR Election on Shared Segments

On any multi-access segment with multiple PIM routers, exactly one of them acts on behalf of hosts: the designated router. The DR sends the (*,G) joins when receivers appear (last-hop DR) and performs registration when sources transmit (first-hop DR). Election is highest DR priority, tie broken by highest IP address, visible in the neighbor table's DR column and adjustable per interface with ip pim dr-priority. Two operational notes: the IGMP querier election (lowest IP, covered in IGMP explained) can land on a different router than the PIM DR, which is normal and confuses everyone once; and if the DR dies, the standby takes over only after the neighbor holdtime expires (default 105 seconds), so DR priority placement matters on segments with real receivers.

Tuning the Switchover (and When to Refuse It)

The ip pim spt-threshold default of 0 is right for almost everyone: shortest paths, minimum latency, RP out of the data path immediately. The infinity setting, pinning receivers to the shared tree as demonstrated above, has legitimate uses: it concentrates state (routers between RP and receivers hold only (*,G) entries no matter how many sources exist), and it makes traffic engineering predictable, since everything flows through one known point. Some designs apply it per group range with an ACL, keeping high-rate feeds on SPTs while low-rate chatter stays shared. The middle-ground kbps thresholds the CLI still accepts are largely historical; modern platforms measure poorly at low rates and the operational advice is binary, 0 or infinity. If you do pin the shared tree, size the RP links accordingly: they are now permanent transit, not just introduction service.

Finding the RP: Static, BSR, Auto-RP

Everything above assumed routers agree on the RP. Three mechanisms get you there. Static (ip pim rp-address 2.2.2.2 on every router) is predictable and fine for small domains; the cost is touching every router to change it. BSR (RFC 5059, standards-based) elects a bootstrap router that collects candidate-RP advertisements and floods the mapping set hop by hop. Auto-RP is the legacy Cisco equivalent over groups 224.0.1.39/40. Adding BSR to the lab took two lines on R2 (ip pim bsr-candidate Loopback0 and ip pim rp-candidate Loopback0), after which every router learned the mapping dynamically:

R1# show ip pim rp mapping
Group(s) 224.0.0.0/4
  RP 2.2.2.2 (?), v2
    Info source: 2.2.2.2 (?), via bootstrap, priority 0, holdtime 150
Group(s): 224.0.0.0/4, Static
    RP: 2.2.2.2 (?)

R1# show ip pim bsr-router
PIMv2 Bootstrap information
  BSR address: 2.2.2.2 (?)
  Uptime:      00:01:05, BSR Priority: 0, Hash mask length: 0

Both sources coexist here; dynamically learned mappings win unless the static entry carries the override keyword. For RP redundancy at scale, anycast RP with MSDP is the standard design, covered in bidir PIM and MSDP.

What the Receiver Saw

Every capture above is control-plane state, so it is worth closing the loop with the data plane. Throughout the register and switchover sequence, the actual receiver, a Debian VM bridged into the lab as a real host, was pulling the stream:

j@llmbits:~$ python3 /tmp/mcast_recv.py 239.1.1.10 5001
joined 239.1.1.10:5001 on 192.168.99.100 (IGMP membership active)
rx 32 bytes from 10.0.30.10: PingLabz mcast 239.1.1.10 pkt 53
rx 32 bytes from 10.0.30.10: PingLabz mcast 239.1.1.10 pkt 54
rx 32 bytes from 10.0.30.10: PingLabz mcast 239.1.1.10 pkt 55

No packets were lost across the switchover: the (S,G) tree was fully built and passing traffic before the shared-tree prune took effect, which is the make-before-break property that lets PIM-SM re-root a live stream invisibly. The sequence numbers just kept incrementing while the network beneath them swapped trees. That is the standard PIM-SM outcome: the mechanism is elaborate, and the application never notices any of it.

Flags Cheat Card

S / C / L
Sparse mode / directly connected member via IGMP / router itself joined the group.
J / T
Join SPT decision made / traffic confirmed arriving on the source tree. JT together = healthy post-switchover state.
F / P
Register flag (first-hop router tunneling to RP) / pruned, OIL is Null. PF on a first hop with no receivers is normal, not broken.
A / R / s
MSDP advertisement candidate / RP-bit (entry toward RP on shared tree) / group is in the SSM range.

Key Takeaways

  • PIM-SM forwards nothing until asked: receivers pull with explicit (*,G) joins toward the RP, sources push their existence with unicast registers to the RP.
  • The RP exists for introductions, not for permanent transit. After the SPT switchover (default: first packet), traffic flows on the shortest path and the RP drops out of the data plane for that receiver.
  • The lifecycle reads directly from show ip mroute flags: SC while waiting, SPF/FT at a registering first hop, TA at the RP, SJC plus JT after switchover.
  • show ip mroute count quantifies the switchover with separate RP-tree and source counters.
  • Static RP, BSR, and Auto-RP are just three ways to distribute the same mapping; every router must agree or joins die quietly. Verify with show ip pim rp mapping everywhere.

Next: the RPF check, the rule underneath every incoming-interface line above. The full cluster is in the complete multicast guide.

Written by
More from Ping Labz
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Ping Labz.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.