Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

PIM Component

Overview

The PIM (Protocol Independent Multicast) component implements Source-Specific Multicast (SSM) as defined in RFC 7761 and RFC 4607. Local host membership is handled by an embedded IGMP instance, which reports its state through its own flockc igmp command, and a host reports interest in a multicast group either via IGMPv3 INCLUDE reports or by receiving Join/Prune messages from downstream PIM neighbors. PIM walks the unicast RIB to find the reverse-path-forwarding (RPF) interface back to each source, sends upstream Joins towards that source, and installs (S,G) entries into the multicast RIB (mRIB) so that flockd can program the kernel multicast forwarding table.

This is the SSM minimum-viable feature set. The following are not supported:

  • Any-Source Multicast (ASM), Rendezvous Points, the Register encapsulation path, or BSR.
  • PIM Assert election — on a LAN with two equal-cost upstream candidates both will forward the same flow.
  • IPv6 multicast.

Groups in the SSM range (default 232.0.0.0/8) are accepted; any other group is dropped.

Configuration

PIM is enabled by adding a pim object to /etc/flockd/flockd.json with one entry per VRF that should run PIM. Each VRF lists the interfaces PIM should run on; PIM is not active on any interface that is not in the list. The embedded IGMP instance is started automatically on every PIM-listed interface.

The minimal config — every field at its RFC default and PIM on a single interface:

"pim": {
    "vrfs": {
        "default": {
            "intfs": {
                "eth0": {}
            }
        }
    }
}

Per-VRF fields

Every field is optional; an omitted field keeps its compile-time default. All timing fields are integer seconds.

FieldDefaultMeaning
ssm_range"232.0.0.0/8"SSM acceptance range (CIDR). Joins for groups outside this range are dropped. RFC 4607.
hello_interval30Hello transmission period in seconds. RFC 7761 § 4.8 Hello_Period.
hold_time105Neighbor Hold Time in seconds advertised in our Hellos (3.5 × hello_interval). RFC 7761 § 4.8 Default_Hello_Holdtime. Range 1–65535; 65535 asks peers never to time this router out. 0 announces that a router is going away, so it is unusable as a standing Hold Time and flockd refuses a config that sets it -- PIM still sends one when it stops on an interface, as described below.
join_prune_interval60Upstream Join/Prune retransmission period in seconds. RFC 7761 § 4.8 t_periodic.
keepalive_period210Per-(S,G) Keepalive Period in seconds. RFC 7761 § 4.8 Keepalive_Period. An (S,G) is aged out after this period unless its liveness is refreshed; local IGMP membership and received Joins refresh it (refresh on data-plane source traffic is not yet wired). Statically joined (S,G) entries (see static_joins) are never aged out.
igmpv2_ssm_map[]Static IGMPv2-to-SSM mapping. A list of { "groups": <CIDR>, "sources": [<addr>, ...] } entries; an IGMPv2 (*,G) report for a group covered by groups is synthesised into an IGMPv3 INCLUDE for each listed source that falls in the SSM range. Lets IGMPv2-only hosts drive SSM joins. RFC 4607 § 4.2.
intfs{}Map of interface name to per-interface PIM configuration. Listing an interface enables PIM on it; an unlisted interface stays idle even if it has a primary IPv4 in the VRF.

Per-interface fields

FieldDefaultMeaning
dr_priority1PIM DR Priority advertised in Hellos on this interface. Higher wins; ties broken by address. RFC 7761 § 4.3.2.
static_joins[]Operator-configured static (S,G) joins on this interface, equivalent to a permanent local membership. A list of { "groups": <CIDR>, "sources": [<addr>, ...] } entries; every (group, source) combination that falls in the SSM range is pinned and forwarded out this interface regardless of host reports. Pinned (S,G) entries are never aged out by the Keepalive timer.

A config that lowers Hello and Hold for a faster-converging lab, narrows the SSM range to a single octet, runs PIM on two interfaces, and tilts the DR election toward eth1:

"pim": {
    "vrfs": {
        "default": {
            "hello_interval": 5,
            "hold_time": 18,
            "ssm_range": "232.10.0.0/16",
            "intfs": {
                "eth0": {},
                "eth1": { "dr_priority": 100 }
            }
        }
    }
}

Static joins and IGMPv2-to-SSM mapping

A receiver with no IGMP-capable host on the link — or one that must keep a flow up regardless of host membership — can be pinned with static_joins on the receiving interface. Each entry lists a group range and one or more sources; every (group, source) combination that falls in the SSM range is joined immediately and never aged out:

"pim": {
    "vrfs": {
        "default": {
            "intfs": {
                "eth0": {
                    "static_joins": [
                        { "groups": "232.1.1.0/24", "sources": ["10.0.3.5", "10.0.3.6"] },
                        { "groups": "232.5.5.5/32", "sources": ["10.0.5.10"] }
                    ]
                }
            }
        }
    }
}

Hosts that can only speak IGMPv2 cannot name a source, so their (*,G) reports carry no SSM source. igmpv2_ssm_map (per VRF) maps such reports onto fixed sources so IGMPv2 receivers can still drive an SSM tree:

"pim": {
    "vrfs": {
        "default": {
            "igmpv2_ssm_map": [
                { "groups": "232.1.1.0/24", "sources": ["10.0.3.5", "10.0.3.6"] }
            ],
            "intfs": {
                "eth0": {}
            }
        }
    }
}

Reload semantics

Most fields take effect immediately:

  • hello_interval — every PIM-enabled interface's recurring Hello timer is re-armed at the new interval.
  • hold_time — appears in every subsequent outbound Hello.
  • ssm_range — applied on reload and reconciled against live state: narrowing the range tears down any existing (S,G) (and static pin) whose group now falls outside it; widening re-adds static pins that come back into range. No flockd restart is needed.
  • join_prune_interval — applied to new (S,G) entries. Already-active (S,G) entries keep their previous retransmit cadence until they are torn down and recreated.
  • keepalive_period — cached at (S,G) creation, so it applies to new (S,G) entries; existing entries keep their current period until recreated.
  • igmpv2_ssm_map — reconciled on reload against live IGMPv2 memberships: sources no longer covered are torn down and newly covered sources build new (S,G) state.
  • intfs — adding an entry enables PIM on the interface immediately if sys has reported a primary IPv4 for it; removing an entry tears the per-interface state down and closes the IGMP / PIM raw sockets. Per-interface dr_priority changes are applied on a fresh Hello; static_joins edits are reconciled in place — added entries pin new (S,G) joins immediately and removed entries drop the pin (tearing the (S,G) down unless a host report or downstream Join still wants it).

Pairing PIM with a unicast IGP

PIM relies on a working unicast routing table to resolve RPF, so it is normally paired with a unicast IGP. Each VRF resolves RPF in its own unicast table and forwards from its own multicast table, so VRFs running PIM are independent of one another. A typical small lab configuration enabling OSPFv2 plus PIM on the default VRF looks like:

{
    "ospfv2": {
        "vrfs": {
            "default": {
                "router_id": "1.1.1.1",
                "areas": {
                    "0.0.0.0": {
                        "intfs": {
                            "lo": {},
                            "r1-n12": {
                                "hello_interval": 1,
                                "ospf_intf_type": "point-to-point"
                            }
                        }
                    }
                }
            }
        }
    },
    "pim": {
        "vrfs": {
            "default": {}
        }
    }
}

Saving flockd.json is enough to apply the change; flockd auto-reloads the file.

Operational State

Check PIM is enabled

PIM appears in the enabled_protocols list in the system overview:

flock@r1:~$ flockc sys overview
{"host_info":{"hostname":"r1", ...},"enabled_protocols":["OSPFv2","PIM","Static"], ...}

Show PIM summary

Aggregate counts for one VRF.

flock@r1:~$ flockc pim vrf default summary
{"enabled_intf_count":2,"neigh_count":1,"sg_count":1,"sg_rpf_unresolved":0}
  • enabled_intf_count — interfaces with PIM currently up.
  • neigh_count — PIM neighbors learnt from inbound Hellos.
  • sg_count — active (S,G) entries.
  • sg_rpf_unresolved — (S,G) entries that are still waiting for the unicast RIB to resolve a route to the source.

Show PIM VRFs

One row per VRF PIM is configured in. A VRF the system has not reported yet is listed with a null vrf_id, a null last_modified and zero counts: PIM holds its configuration but is not running there.

flock@r1:~$ flockc pim vrfs
[{"vrf_name":"default","vrf_id":254,"enabled_intf_count":2,"neigh_count":1,"sg_count":1,
  "sg_rpf_unresolved":0,"last_modified":"2026-08-03T09:14:22.481Z"}]

Show a PIM interface

Per-interface snapshot including the neighbor table. Group membership is reported by IGMP, on its own commands.

flock@r1:~$ flockc pim vrf default intf r1-n12
{"intf_id":12,"enabled":true,
 "neighs":[{"intf_id":12,"addr":"10.0.12.2","dr_priority":1,"gen_id":3735928559,
            "hold_time":{"secs":105,"nanos":0},
            "hold_remaining":{"secs":97,"nanos":314000000},
            "last_modified":"2026-08-03T09:14:22.481Z"}],
 "last_modified":"2026-08-03T09:14:22.481Z"}

Show a PIM neighbor

Look up a single neighbor by interface name and neighbor address.

flock@r1:~$ flockc pim vrf default neigh r1-n12 10.0.12.2
{"intf_id":12,"addr":"10.0.12.2","dr_priority":1,"gen_id":3735928559,
 "hold_time":{"secs":105,"nanos":0},
 "hold_remaining":{"secs":97,"nanos":314000000},
 "last_modified":"2026-08-03T09:14:22.481Z"}

A PIM neighbor has no up/down state: it appears on its first Hello and is removed when its hold timer fires or when it announces its departure, so a row being present is what "up" means. hold_time is the Hold Time the neighbor advertises and hold_remaining counts down towards ageing it out (RFC 7761 § 4.9.2). Every Hello returns hold_remaining to hold_time, which makes it the field to read for liveness -- last_modified is not, because a Hello re-advertising the same values only re-arms the timer and leaves the stamp where it is.

PIM makes the same announcement itself. When an interface leaves PIM configuration, loses carrier, or the daemon shuts down, it sends a Hello carrying a Hold Time of 0 on every interface it was running on, so neighbors drop this router as it goes rather than a Hold Time later. Adjacencies elsewhere therefore clear as soon as flockd stops. This is not configurable and is not the same as setting hold_time to 0, which the table above refuses.

hold_remaining is null when the neighbor holds no expiry timer, which means "no deadline", never "no time left". Two cases produce it. A neighbor advertising the reserved Hold Time of 65535 s is asking never to be timed out, and is kept indefinitely with no timer running; hold_time reads 65535 s and hold_remaining stays null for as long as it is up. Otherwise it is the brief window after the hold timer has fired while the neighbor is being removed. A neighbor advertising a Hold Time of 0 is announcing that it is going away and is removed as the Hello is processed, so it never appears with that value.

Stream interface changes

flockc subscribe pim intf streams per-interface changes as they happen, printing one update per change until interrupted. It first prints the current state of every interface it covers, then each add, change and delete. --vrf limits the stream to one VRF and --intf-name to interfaces of that name; with neither, every PIM interface in every VRF is streamed.

flock@r1:~$ flockc subscribe pim intf --vrf default
{"intf_name":"r1-n12","vrf_name":"default","value":{"up":true,"intf_addr":"10.0.12.1","dr":"10.0.12.2","neigh_count":1,"last_modified":"2026-08-03T09:14:22.481Z"},"deleted":false}
{"intf_name":"r1-n13","vrf_name":"default","value":{"up":false,"neigh_count":0,"last_modified":"2026-08-03T09:15:01.007Z"},"deleted":false}

Every update names the interface and the VRF it is in. up is false for an interface that is configured and known to the system but on which PIM is not running, for example because it has no primary IPv4 address or its carrier is down; intf_addr and dr are omitted while it is down. dr is the elected Designated Router, and equals intf_addr when this router holds the role. An interface that leaves PIM configuration, or that the system removes, is reported with "deleted":true and no value.

List all (S,G) entries

Every active (S,G) in one VRF.

flock@r1:~$ flockc pim vrf default rib
[{"vrf_name":"default","vrf_id":254,"source":"192.0.2.10","group":"232.1.1.1",
  "iif":12,"oifs":[14],"rpf_resolved":true,
  "keepalive_remaining":{"secs":205,"nanos":0},
  "last_modified":"2026-08-03T09:14:22.481Z"}]

Every row carries a last_modified recording when the state that row reports last changed, to millisecond resolution. A refresh that changes nothing -- a periodic Hello, an IGMP report re-affirming a source already joined -- leaves it where it is.

iif is the RPF interface; oifs is the union of IGMP-driven and downstream-PIM-Join-driven membership. If rpf_resolved is false the entry exists but no upstream Join has been emitted and no forwarding state has been programmed. keepalive_remaining counts down to the per-(S,G) Keepalive expiry (RFC 7761 § 4.1.6) and is refreshed by local membership and received Joins; it is null only in the brief window after the Keepalive has fired and the entry is being torn down.

View the multicast RIB

The mRIB is the routing-table view that flockd uses to program the kernel multicast forwarding table. It has its own flockc mrib command, described in the Multicast RIB Component chapter.

List every (S,G) in a VRF's mRIB. The mRIB holds a table per address family, and the walk selects one; it defaults to IPv4:

flock@r1:~$ flockc mrib vrf default walk
[{"source":"192.0.2.10","group":"232.1.1.1","origin":"Pim","iif":12,"oifs":[14],
  "last_modified":"2026-08-03T09:14:22.481Z"}]

Look up a specific (S,G):

flock@r1:~$ flockc mrib vrf default lookup 192.0.2.10 232.1.1.1
{"source":"192.0.2.10","group":"232.1.1.1","origin":"Pim","iif":12,"oifs":[14],
 "last_modified":"2026-08-03T09:14:22.481Z"}

PIM Operation Commands Reference

Help

flockc pim --help

VRFs

flockc pim vrfs

Summary

flockc pim vrf <vrf-name> summary

Interface

flockc pim vrf <vrf-name> intf <intf-name>

Neighbor

flockc pim vrf <vrf-name> neigh <intf-name> <neighbor-address>

All (S,G) entries

flockc pim vrf <vrf-name> rib

Interface change stream

flockc subscribe pim intf [--vrf <vrf-name>] [--intf-name <intf-name>]

mRIB walk / lookup

flockc mrib vrf <vrf-name> walk [--af ipv4|ipv6]
flockc mrib vrf <vrf-name> lookup <source> <group>