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:
- Stop the state and the packets at the boundary interface, or
- 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:
outonly 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.10falls inside239.64.0.0/10— a reserved block with no standard scope at all. Using it has no RFC basis.239.193.10.10sits inside239.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> outon the campus exit interfaces of both border routers — per-group filtering enforced on control plane and data plane. - Part (b):
239.65.10.10lands in the reserved239.64.0.0/10;239.193.10.10is fine (organization-local) but the two straddle scope blocks, so no single standard boundary covers both — pick both groups from239.192.0.0/14instead. - 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 -tto show why TTL scoping is unreliable.