Keeping multicast home: TTL scoping vs. the multicast boundary


Every so often I go back to Routing TCP/IP and work an exercise I thought I already knew. Chapter 7’s first configuration exercise looks trivial — “keep two multicast groups inside the campus” — but it’s actually a compressed lesson in the difference between dropping packets and preventing state from ever existing. This post is my solution analysis plus a paste-ready EVE-NG lab that proves it.

The exercise

A company runs two multicast applications. Two Class D addresses have been assigned: 239.65.10.10 and 239.193.10.10. The requirement: this traffic must stay inside the campus — the branches and the core must not be transit.

Part (a): how do you restrain it? Part (b): why are these two addresses a poor choice?

Why this isn’t an ACL problem

The reflexive answer is “just ACL it.” But multicast forwarding is different from unicast in one crucial way: the source sends one packet and the router replicates it out every outgoing interface in the (*,G)/(S,G) entry. That state is built by PIM — flood-and-prune in dense mode, trees toward the RP or the source in sparse mode.

So “multicast leaking out of the campus” means forwarding state got built toward the core. There are exactly two places to intercept that:

  1. Stop the state and the packets at the boundary interface, or
  2. Keep the traffic from ever qualifying to go there — relying on the packet’s own TTL, or on address semantics.

IOS gives you one classic tool for each: TTL scoping (ip multicast ttl-threshold) and administrative scoping (ip multicast boundary).

The two tools, side by side

ttl-threshold multicast boundary
Config ip multicast ttl-threshold <1-255> ip multicast boundary <ACL> [out|in]
Matches on The packet’s TTL The group address
Precision Coarse — stops all multicast with a low TTL, regardless of application Exact per group
Precondition The application must set its TTL, or it silently fails Purely network-side; apps are unaware
Control plane PIM untouched — state still gets built; only data is dropped Filters PIM joins/registers too — no state is built across the boundary at all
Standards basis None (a hop-count metaphor) RFC 2365 administrative scopes

The exercise hands you explicit group addresses and demands per-application containment — which points straight at the boundary:

access-list 10 deny 239.65.10.10
access-list 10 deny 239.193.10.10
access-list 10 permit any
!
interface Ethernet0/1          ! campus-facing uplink on BOTH border routers
 ip multicast boundary 10 out

A few things worth calling out:

  • out only restricts forwarding toward the core; the campus side is unaffected.
  • With no direction keyword the boundary filters both ways, PIM control packets included — use that when “nothing may come in from outside” is also a requirement.
  • The boundary matches on group address and doesn’t care what the application does with TTL. That independence is exactly why it wins here.

And the alternatives? Not running multicast in the core contradicts the premise. SSM range restriction solves “source-specific,” not scoping. Auto-RP scoping (send-rp-announce ... scope) is a complement that keeps RP information from leaking, not a replacement. The group address is the only stable, controllable handle this exercise gives you — which is why the address-based boundary is the standard answer.

Part (b): the addresses were the problem all along

RFC 2365 / RFC 3171 carve up 239.0.0.0/8 like this:

Range Use
239.0.0.0/10 · 239.64.0.0/10 · 239.128.0.0/10 Reserved — not for ad-hoc use
239.192.0.0/14 Organization-Local
239.252.0.0/14 Site-Local
239.255.0.0/16 Node-Local
  • 239.65.10.10 falls inside 239.64.0.0/10 — a reserved block with no standard scope at all. Using it has no RFC basis.
  • 239.193.10.10 sits inside 239.192.0.0/14 — that one’s actually fine.
  • Worse, the two addresses straddle different scopes, so no single boundary drawn on a standard scope block covers both. You’re stuck listing groups one by one in an ACL, which is exactly the kind of thing that breaks quietly at 2 a.m.

The better choice: take both groups from 239.192.0.0/14 (say 239.192.10.10 and 239.192.10.11). One organization-local boundary covers them, and the semantics are unambiguous.

The lab

Book topology, EVE-NG reality: the book’s Catalina is a switch and the source/receiver are hosts. In my lab every node is a router — IOL and IOLL2 images. Two lab tricks make it work:

  • A router with ip igmp join-group <G> answers multicast pings, which turns it into a verifiable group member.
  • The core’s loopback joins the groups too, so we can watch the leak happen.

Unicast reachability is one OSPF area 0 across the whole topology (multicast RPF depends on the unicast table), multicast is PIM-SM with a static RP on San_Jose’s loopback, and the “application traffic” is just multicast pings from the source.

                        +----------------------+
                        |        Core          |
                        |      Lo0 9.9.9.9/32  |   <- IGMP join: the leak detector
                        +----------+-----------+
                         E0/0 |         | E0/1
                 192.168.12.2 |         | 192.168.23.1
                              |         |
        +---------------------+--+   +--+---------------------+
 CAMPUS |  San_Jose   E0/1 ------+   +------ E0/1   San_Diego |
        |  Lo0 1.1.1.1 (RP)                       E0/0        |
        |  E0/0 10.1.1.1                       10.1.1.2        |
        +------+---------------------------------------+--------+
               |          Catalina (IOL L2)            |
               |          VLAN 1: 10.1.1.0/24          |
        10.1.1.100                                10.1.1.200
        Multicast Source                    Multicast Receiver
        (sends pings)                       (IGMP join, both groups)

The teal interface in the book’s figure — here E0/1 on both border routers — is where ip multicast boundary 10 out goes.

Node configs (paste-ready)

R1 — San_Jose (also the RP):

hostname San_Jose
!
ip multicast-routing
!
interface Loopback0
 ip address 1.1.1.1 255.255.255.255
 ip pim sparse-mode
!
interface Ethernet0/0
 ip address 10.1.1.1 255.255.255.0
 ip pim sparse-mode
 no shutdown
!
interface Ethernet0/1
 ip address 192.168.12.1 255.255.255.0
 ip pim sparse-mode
 no shutdown
!
router ospf 1
 network 0.0.0.0 255.255.255.255 area 0
!
ip pim rp-address 1.1.1.1

R2 — San_Diego: same shape — E0/0 10.1.1.2/24, E0/1 192.168.23.2/24, both ip pim sparse-mode, same OSPF and same ip pim rp-address 1.1.1.1.

R3 — Core. The loopback join is the whole trick: it emulates a receiver living in the core:

hostname Core
!
ip multicast-routing
!
interface Loopback0
 ip address 9.9.9.9 255.255.255.255
 ip pim sparse-mode
 ip igmp join-group 239.65.10.10
 ip igmp join-group 239.193.10.10
!
interface Ethernet0/0
 ip address 192.168.12.2 255.255.255.0
 ip pim sparse-mode
 no shutdown
!
interface Ethernet0/1
 ip address 192.168.23.1 255.255.255.0
 ip pim sparse-mode
 no shutdown
!
router ospf 1
 network 0.0.0.0 255.255.255.255 area 0
!
ip pim rp-address 1.1.1.1

R4 — Multicast Source is a plain unicast node: just E0/0 10.1.1.100/24, no multicast config at all.

R5 — Multicast Receiver joins both groups:

hostname Multicast-Receiver
!
ip multicast-routing
!
interface Ethernet0/0
 ip address 10.1.1.200 255.255.255.0
 ip pim sparse-mode
 ip igmp join-group 239.65.10.10
 ip igmp join-group 239.193.10.10
 no shutdown
!
ip pim rp-address 1.1.1.1

SW — Catalina (IOL L2): every port ships in VLAN 1 and up. No config needed — show interfaces status to confirm.

If you’d rather use vIOS/vIOS-L2, it works the same — just substitute GigabitEthernet for Ethernet throughout.

Three acts

Act 1 — reproduce the leak. From the source:

Multicast-Source# ping 239.65.10.10 source e0/0 repeat 9999 timeout 1
Multicast-Source# ping 239.193.10.10 source e0/0 repeat 9999 timeout 1

The receiver and the core’s loopback both answer. That is precisely the behavior the exercise asks us to prevent — the traffic has reached the core. Confirm the state on R1:

San_Jose# show ip mroute 239.65.10.10
(*, 239.65.10.10), ...
  Incoming interface: Ethernet0/0 (RPF)
  Outgoing interface list:
    Ethernet0/1, Forward/Sparse, ...    <-- state built toward the core

Act 2 — apply the boundary. On both R1 and R2:

access-list 10 deny 239.65.10.10
access-list 10 deny 239.193.10.10
access-list 10 permit any
!
interface Ethernet0/1
 ip multicast boundary 10 out

Clear state to speed convergence (clear ip mroute *), retest, and check:

Check Command / action Expected
Receiver still receives re-run the multicast ping replies as before
Core receives nothing same no replies
OIF disappears show ip mroute 239.65.10.10 E0/1 gone from the outgoing list
ACL hits climb show access-lists 10 matches increase on the deny lines
Boundary active show ip multicast boundary ACL 10 listed on E0/1

Act 3 (optional) — the TTL contrast. Remove the boundary and put ip multicast ttl-threshold 15 on the same interfaces. IOS ping can’t set a custom TTL, so the easiest source is a Linux Docker node hanging off Catalina at 10.1.1.100/24:

ping -t 10 239.65.10.10    # TTL=10 < 15 -> cannot leave the campus
ping -t 20 239.65.10.10    # TTL=20 > 15 -> reaches the core

For the very same group, whether traffic crosses the boundary now depends on the TTL chosen by whoever sends it. That’s the whole argument against TTL scoping made visible.

Troubleshooting notes

Symptom What to check, in order
Receiver gets nothing show ip igmp groups (is R5 in the group?) → show ip pim neighbor (all three routers full?) → show ip pim rp mapping (RP info consistent?)
Core should receive but doesn’t show ip mroute — does Core have (*,G) and an OIF? Check the ip igmp join-group on Core Lo0
Boundary set but core still receives Confirm it’s on E0/1 (the core-facing port) with the out direction; retest after clear ip mroute *; watch matches climb on show access-lists 10
RPF failure show ip rpf 10.1.1.100; confirm the OSPF adjacencies are full

The key mechanism underneath all of it: with out, the boundary filters outbound data and control packets — PIM joins are stopped, so no state is ever built toward the core. That’s what makes the boundary smarter than a TTL threshold: it doesn’t just drop data, it prevents the state from existing.

One-page summary

  • What’s tested: multicast scoping — TTL scoping vs. administrative scoping.
  • The solution: ip multicast boundary <ACL> out on the campus exit interfaces of both border routers — per-group filtering enforced on control plane and data plane.
  • Part (b): 239.65.10.10 lands in the reserved 239.64.0.0/10; 239.193.10.10 is fine (organization-local) but the two straddle scope blocks, so no single standard boundary covers both — pick both groups from 239.192.0.0/14 instead.
  • Lab essence: join the groups on Core Lo0 to watch the leak → apply the boundary and watch the core go silent while the campus keeps working → (optional) TTL threshold plus Linux ping -t to show why TTL scoping is unreliable.