RFC 7761
Protocol Independent Multicast - Sparse Mode (PIM-SM): Protocol Specification (Revised)
RFC 7761 ยท PIM-SM Protocol Specification (Revised) ๐ฆโจ
Okay bestie, let's start with the basics. This is RFC 7761, Protocol Independent Multicast - Sparse Mode (PIM-SM): Protocol Specification (Revised), published in March 2016 as a Standards Track document in the STD 83 series (ISSN 2070-1721). ๐ It was written by seven authors: B. Fenner (Arista Networks), M. Handley (UCL), H. Holbrook (Arista Networks), I. Kouvelas (Arista Networks), R. Parekh (Cisco Systems, Inc.), Z. Zhang (Juniper Networks), and L. Zheng (Huawei Technologies). ๐ It obsoletes RFC 4601, which was the previous PIM-SM spec, and the title block doesn't list anything it updates. ๐
Internet Engineering Task Force (IETF) B. Fenner
Request for Comments: 7761 Arista Networks
STD: 83 M. Handley
Obsoletes: 4601 UCL
Category: Standards Track H. Holbrook
ISSN: 2070-1721 I. Kouvelas
Arista Networks
R. Parekh
Cisco Systems, Inc.
Z. Zhang
Juniper Networks
L. Zheng
Huawei Technologies
March 2016
Abstract ๐ธ
Okay so this document specifies Protocol Independent Multicast - Sparse Mode (PIM-SM), which is a multicast routing protocol that can lean on the unicast routing information base or a totally separate multicast-capable routing information base. ๐ง It builds unidirectional shared trees rooted at a Rendezvous Point (RP) for each group, and it can optionally create shortest-path trees per source. Iconic. ๐
It also obsoletes RFC 4601 by replacing it, deals with the errata that were filed against it, and drops a few features that haven't had enough real-world deployment yet (see Appendix A). Those are the optional (,,RP), the PIM Multicast Border Router features, and authentication using IPsec. ๐ญ Finally, it moves the PIM specification up to Internet Standard. We're so proud of it. ๐
Status of This Memo โจ
This is an Internet Standards Track document. ๐
This document is a product of the Internet Engineering Task Force (IETF). ๐ It represents the consensus of the IETF community, it got public review, and the Internet Engineering Steering Group (IESG) approved it for publication. If you want more info on Internet Standards, check out Section 2 of RFC 5741 ๐
For the latest status of this document, any errata, and how to send feedback, go to http://www.rfc-editor.org/info/rfc7761. ๐ฆ
Copyright Notice ๐ฅ
Copyright (c) 2016 IETF Trust and the persons identified as the document authors. All rights reserved. ๐
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (http://trustee.ietf.org/license-info) as they stand on the date this document was published. Please actually read these, bestie, because they spell out your rights and restrictions for this document. ๐ซถ Any Code Components pulled out of this document must include the Simplified BSD License text, as described in Section 4.e of the Trust Legal Provisions, and they come with no warranty, as described in the Simplified BSD License. ๐
Table of Contents ๐ง
1. Introduction ....................................................5
2. Terminology .....................................................5
2.1. Definitions ................................................5
2.2. Pseudocode Notation ........................................7
3. PIM-SM Protocol Overview ........................................7
3.1. Phase One: RP Tree .........................................8
3.2. Phase Two: Register-Stop ...................................9
3.3. Phase Three: Shortest-Path Tree ...........................10
3.4. Source-Specific Joins .....................................10
3.5. Source-Specific Prunes ....................................11
3.6. Multi-Access Transit LANs .................................11
3.7. RP Discovery ..............................................12
4. Protocol Specification .........................................12
4.1. PIM Protocol State ........................................13
4.1.1. General-Purpose State ..............................14
4.1.2. (*,G) State ........................................15
4.1.3. (S,G) State ........................................17
4.1.4. (S,G,rpt) State ....................................19
4.1.5. State Summarization Macros .........................20
4.2. Data Packet Forwarding Rules ..............................24
4.2.1. Last-Hop Switchover to the SPT .....................27
4.2.2. Setting and Clearing the (S,G) SPTbit ..............27
4.3. Designated Routers (DRs) and Hello Messages ...............29
4.3.1. Sending Hello Messages .............................29
4.3.2. DR Election ........................................31
4.3.3. Reducing Prune Propagation Delay on LANs ...........33
4.3.4. Maintaining Secondary Address Lists ................36
4.4. PIM Register Messages .....................................37
4.4.1. Sending Register Messages from the DR ..............38
4.4.2. Receiving Register Messages at the RP ..............43
4.5. PIM Join/Prune Messages ...................................44
4.5.1. Receiving (*,G) Join/Prune Messages ................45
4.5.2. Receiving (S,G) Join/Prune Messages ................50
4.5.3. Receiving (S,G,rpt) Join/Prune Messages ............54
4.5.4. Sending (*,G) Join/Prune Messages ..................61
4.5.5. Sending (S,G) Join/Prune Messages ..................65
4.5.6. (S,G,rpt) Periodic Messages ........................71
4.5.7. State Machine for (S,G,rpt) Triggered Messages .....72
4.6. PIM Assert Messages .......................................76
4.6.1. (S,G) Assert Message State Machine .................77
4.6.2. (*,G) Assert Message State Machine .................85
4.6.3. Assert Metrics .....................................93
4.6.4. AssertCancel Messages ..............................94
4.6.5. Assert State Macros ................................95
4.7. PIM Bootstrap and RP Discovery ............................98
4.7.1. Group-to-RP Mapping ................................99
4.7.2. Hash Function .....................................100
4.8. Source-Specific Multicast ................................101
4.8.1. Protocol Modifications for SSM Destination
Addresses .........................................102
4.8.2. PIM-SSM-Only Routers ..............................102
4.9. PIM Packet Formats .......................................104
4.9.1. Encoded Source and Group Address Formats ..........105
4.9.2. Hello Message Format ..............................108
4.9.3. Register Message Format ...........................111
4.9.4. Register-Stop Message Format ......................113
4.9.5. Join/Prune Message Format .........................114
4.9.5.1. Group Set Source List Rules ..............117
4.9.5.2. Group Set Fragmentation ..................120
4.9.6. Assert Message Format .............................121
4.10. PIM Timers ..............................................122
4.11. Timer Values ............................................124
5. IANA Considerations ...........................................130
5.1. PIM Address Family .......................................130
5.2. PIM Hello Options ........................................130
6. Security Considerations .......................................131
6.1. Attacks Based on Forged Messages .........................131
6.1.1. Forged Link-Local Messages ........................131
6.1.2. Forged Unicast Messages ...........................132
6.2. Non-cryptographic Authentication Mechanisms ..............132
6.3. Authentication ...........................................133
6.4. Denial-of-Service Attacks ................................133
7. References ....................................................133
7.1. Normative References .....................................133
7.2. Informative References ...................................134
Appendix A. Functionality Removed from RFC 4601 ..................136
Acknowledgements .................................................136
Authors' Addresses ...............................................136
List of Figures (Shown in Tabular Form)
Figure 1. Per-(S,G) Register State Machine at a DR ................39
Figure 2. Downstream Per-Interface (*,G) State Machine ............47
Figure 3. Downstream Per-Interface (S,G) State Machine ............51
Figure 4. Downstream Per-Interface (S,G,rpt) State Machine ........56
Figure 5. Upstream (*,G) State Machine ............................62
Figure 6. Upstream (S,G) State Machine ............................66
Figure 7. Upstream (S,G,rpt) State Machine for Triggered
Messages ................................................72
Figure 8. Per-Interface (S,G) Assert State Machine ................78
Figure 9. Per-interface (*,G) Assert State Machine ................87
1. Introduction ๐
This document specifies a protocol for efficiently routing multicast groups that may span wide-area (and inter-domain) internets. ๐ฆ It's called Protocol Independent Multicast - Sparse Mode (PIM-SM) because, even though it may use the underlying unicast routing to get reverse-path information for building multicast trees, it isn't tied to any particular unicast routing protocol. That's the main character energy right there. ๐
PIM-SM Version 2 was specified in RFC 4601 as a Proposed Standard. ๐ธ This document is here to address the reported errata, remove the optional (,,RP), PIM Multicast Border Router features, and authentication using IPsec that doesn't have enough deployment experience yet, and advance PIM-SM to Internet Standard. ๐
This document specifies the same protocol as RFC 4601, so implementations written to this spec will be able to interoperate successfully with implementations written to RFC 4601. ๐ซถโจ
2. Terminology ๐
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 [1]. ๐๐
2.1. Definitions ๐
okay so these are the key terms for PIM-SM, bestie, and they're lowkey the foundation of everything ๐
- Rendezvous Point (RP): an RP is a router that's been set up to be the root of the non-source-specific distribution tree for a multicast group. Join messages from receivers for a group head towards the RP, and data from senders gets sent to the RP too. That's how receivers figure out who the senders are and start getting traffic for the group. main RP energy ๐
- Designated Router (DR): a shared-media LAN like Ethernet can have multiple PIM-SM routers plugged into it. Just one of them, the DR, acts on behalf of the directly connected hosts when it comes to PIM-SM. A single DR gets elected per interface (LAN or otherwise) using a simple election process. ๐ฆ
- MRIB: Multicast Routing Information Base. It's the multicast topology table, usually built from the unicast routing table, or from routing protocols like Multiprotocol BGP (MBGP) that carry multicast-specific topology info. In PIM-SM, the MRIB decides where Join/Prune messages get sent. It also gives routing metrics for destination addresses, and those metrics get used when sending and processing Assert messages. ๐ธ
- RPF Neighbor: RPF stands for "Reverse Path Forwarding". The RPF Neighbor of a router for an address is the neighbor the MRIB says should be used to forward packets to that address. For a PIM-SM multicast group, the RPF neighbor is the router a Join message for that group would be directed to, as long as no Assert state is modifying that. ๐ง
- TIB: Tree Information Base. This is the collection of state at a PIM router that's built from receiving PIM Join/Prune messages, PIM Assert messages, and Internet Group Management Protocol (IGMP) or Multicast Listener Discovery (MLD) info from local hosts. It basically stores the state of all multicast distribution trees at that router. ๐
- MFIB: Multicast Forwarding Information Base. The TIB holds all the state needed to forward multicast packets at a router. BUT, even though this spec defines forwarding in terms of the TIB, actually forwarding with the TIB is super inefficient. So a real router implementation will normally build an efficient MFIB from the TIB state to do the forwarding. How that's done is implementation-specific, and it's not discussed in this document. ๐
- Upstream: towards the root of the tree. The root may be either the source or the RP, depending on the context.
- Downstream: away from the root of the tree.
- GenID: Generation Identifier, used to detect reboots. ๐
2.2. Pseudocode Notation ๐ง
we use set notation in a few places in this spec, so here's the cheat sheet:
A (+) B the union of two sets, A and B
A (-) B the elements of set A that are not in set B
NULL the empty set or list
we also borrow some C-like syntax:
= assignment of a variable
== comparison for equality
!= comparison for inequality
Braces { and } are used for grouping. ๐
Unless otherwise noted, operations specified by statements with multiple (+) and (-) operators should be evaluated from left to right. So A (+) B (-) C is the set you get from the union of sets A and B, minus the elements in set C. โจ
3. PIM-SM Protocol Overview ๐
this section gives an overview of PIM-SM behavior. It's meant as an intro to how PIM-SM works, and it is not definitive. For the definitive spec, see Section 4. ๐
PIM relies on an underlying topology-gathering protocol to fill a routing table with routes. This routing table is called the Multicast Routing Information Base (MRIB). The routes in this table may come straight from the unicast routing table, or they may be different and provided by a separate routing protocol like MBGP [10].
Regardless of how it's created, the MRIB's main job in PIM is to provide the next-hop router along a multicast-capable path to each destination subnet. The MRIB is used to figure out the next-hop neighbor that any PIM Join/Prune message gets sent to. Data flows along the reverse path of the Join messages. So unlike the unicast RIB, which tells you the next hop a data packet would take to reach some subnet, the MRIB gives you reverse-path info. It shows the path a multicast data packet would take from its origin subnet to the router that has the MRIB. it's giving backwards, but make it make sense ๐
Like all multicast routing protocols that implement the service model from RFC 1112 [3], PIM-SM must be able to route data packets from sources to receivers without either the sources or receivers knowing a priori that the others exist. This is basically done in three phases. Senders and receivers can come and go at any time, so all three phases may happen at once. ๐ฆ
3.1. Phase One: RP Tree ๐ธ
in phase one, a multicast receiver expresses its interest in receiving traffic for a multicast group. Typically it does this using IGMP [2] or MLD [4], but other mechanisms might also do the job. One of the receiver's local routers gets elected as the Designated Router (DR) for that subnet. When the DR gets the receiver's expression of interest, it sends a PIM Join message towards the RP for that multicast group.
This Join message is known as a (*,G) Join, because it joins group G for all sources to that group. The (*,G) Join travels hop-by-hop towards the RP for the group, and at each router it passes through, multicast tree state for group G gets created. Eventually the (*,G) Join either reaches the RP or reaches a router that already has (*,G) Join state for that group. ๐ง
When lots of receivers join the group, their Join messages converge on the RP and form a distribution tree for group G that's rooted at the RP. This is the RP Tree (RPT), also called the shared tree, because every source sending to that group shares it. Join messages get resent periodically as long as the receiver stays in the group. When all receivers on a leaf-network leave the group, the DR sends a PIM (*,G) Prune message towards the RP for that multicast group. But if the Prune message isn't sent for any reason, the state will eventually time out. โฐ
A multicast data sender just starts sending data for a multicast group. The sender's local router (the DR) takes those data packets, unicast-encapsulates them, and sends them directly to the RP. The RP receives these encapsulated data packets, decapsulates them, and forwards them onto the shared tree. The packets then follow the (*,G) multicast tree state in the routers on the RP Tree, getting replicated wherever the RP Tree branches, until they reach all the receivers for that multicast group. The process of encapsulating data packets to the RP is called registering, and the encapsulation packets are known as PIM Register packets. ๐ฆ
At the end of phase one, multicast traffic is flowing encapsulated to the RP, and then natively over the RP tree to the multicast receivers. โจ
3.2. Phase Two: Register-Stop ๐
register-encapsulation of data packets is inefficient for two reasons:
- Encapsulation and decapsulation may be relatively expensive operations for a router to perform, depending on whether or not the router has the right hardware for these tasks.
- Traveling all the way to the RP and then back down the shared tree may mean the packets travel a relatively long distance to reach receivers that are close to the sender. For some applications, this extra latency or bandwidth consumption is undesirable. ๐
Although Register-encapsulation may continue indefinitely, for these reasons the RP will normally choose to switch to native forwarding. To do this, when the RP receives a register-encapsulated data packet from source S on group G, it will normally initiate an (S,G) source-specific Join towards S. This Join message travels hop-by-hop towards S, creating (S,G) multicast tree state in the routers along the path. (S,G) multicast tree state is only used to forward packets for group G if those packets come from source S. Eventually the Join message reaches S's subnet or a router that already has (S,G) multicast tree state. Then packets from S start flowing along the (S,G) tree state towards the RP. These data packets may also reach routers with (*,G) state along the path towards the RP. If they do, they can shortcut onto the RP tree at that point. ๐
While the RP is busy joining the source-specific tree for S, the data packets keep getting encapsulated to the RP. When packets from S also start arriving natively at the RP, the RP is receiving two copies of each of these packets. At that point, the RP starts discarding the encapsulated copy of these packets, and it sends a Register-Stop message back to S's DR to stop the DR from unnecessarily encapsulating the packets. ๐
At the end of phase two, traffic flows natively from S along a source-specific tree to the RP, and from there along the shared tree to the receivers. Where the two trees intersect, traffic may transfer from the source-specific tree to the RP tree and avoid taking a long detour via the RP. ๐
Note that a sender may start sending before or after a receiver joins the group, so phase two may happen before the shared tree to the receiver is built. ๐
3.3. Phase Three: Shortest-Path Tree ๐ธ
okay so the RP joining back toward the source gets rid of the encapsulation overhead โจ, but it doesn't fully optimize the forwarding paths ๐. for a lot of receivers, the route through the RP can be a seriously big detour compared to the shortest path from the source to the receiver ๐
so to get lower latency or use bandwidth more efficiently, a router on the receiver's LAN (usually the DR) can optionally switch from the shared tree to a source-specific shortest-path tree (SPT) ๐ฆ it does this by sending an (S,G) Join toward S. that sets up state in the routers along the path to S. eventually the join either reaches S's subnet or hits a router that already has (S,G) state. once that happens, data packets from S start flowing along the (S,G) state until they reach the receiver ๐ฅ
now, at this point the receiver (or a router upstream of it) is getting two copies of the data: one from the SPT and one from the RPT ๐ญ the moment the first traffic shows up from the SPT, the DR (or upstream router) starts dropping the packets for G from S that arrive over the RP tree. it also sends an (S,G) Prune toward the RP, which is called an (S,G,rpt) Prune ๐
that Prune goes hop-by-hop and sets up state along the path toward the RP saying that traffic from S for G should NOT be forwarded in this direction. it keeps getting propagated until it reaches the RP or a router that still needs S's traffic for other receivers ๐ง
by now the receiver is getting traffic from S along the shortest-path tree between itself and S โจ the RP is still receiving traffic from S, but that traffic doesn't reach the receiver over the RP tree anymore. from the receiver's point of view, this is the final distribution tree ๐
3.4. Source-Specific Joins ๐
okay so IGMPv3 lets a receiver join a group and say "I only want traffic for this group if it comes from one particular source" ๐ if a receiver does that, and no other receiver on the LAN needs all the traffic for the group, then the DR can skip the (*,G) join that would build the shared tree and just send a source-specific (S,G) join ๐ซถ
the range 232.0.0.0 through 232.255.255.255 is currently set aside for source-specific multicast in IPv4 ๐ for groups in that range, receivers should only issue source-specific IGMPv3 joins. and if a PIM router gets a non-source-specific join for a group in that range, it should ignore it ๐
3.5. Source-Specific Prunes ๐ฆ
IGMPv3 also lets a receiver join a group and say "I want this group's traffic only if it does NOT come from a specific source or sources" ๐
in that case the DR still does a (*,G) join as normal, but it may combine that with an (S,G,rpt) prune for each source the receiver doesn't want ๐
3.6. Multi-Access Transit LANs ๐ธ
so far the overview has been about point-to-point transit links ๐ but using a multi-access LAN like Ethernet for transit is super common. and that can cause complications for three reasons ๐
- Two or more routers on the LAN may issue
(*,G)Joins to different upstream routers on the LAN because they have inconsistent MRIB entries about how to reach the RP. both paths on the RP tree end up getting set up, which means two copies of all the shared tree traffic show up on the LAN ๐ญ - Two or more routers on the LAN may issue
(S,G)Joins to different upstream routers on the LAN because they have inconsistent MRIB entries about how to reach sourceS. both paths on the source-specific tree get set up, so two copies of all the traffic fromSshow up on the LAN ๐ฅ - A router on the LAN may send a
(*,G)Join to one upstream router on the LAN, while another router on the LAN sends an(S,G)Join to a different upstream router on the same LAN. traffic fromScan then reach the LAN over both the RPT and the SPT ๐ซ if the receiver behind the downstream(*,G)router doesn't send an(S,G,rpt)prune, this condition would just keep going ๐ง
all of these problems come from having more than one upstream router with join state for the group or the source-group pair โจ PIM doesn't stop these duplicate joins from happening. instead, when duplicate data packets show up on the LAN from different routers, those routers notice and then elect a single forwarder ๐ that election uses PIM Assert messages, which settle things in favor of the upstream router that has (S,G) state. if neither router has (S,G) state, or if both do, then it goes to the router with the best metric to the RP for RP trees, or the best metric to the source for source-specific trees ๐
the downstream routers on the LAN also receive these Assert messages, and that causes their subsequent Join messages to go to whichever upstream router won the Assert ๐
3.7. RP Discovery ๐ฆ
PIM-SM routers need to know the address of the RP for each group they have (*,G) state for ๐ that address can be obtained automatically (e.g., embedded-RP), through a bootstrap mechanism, or through static configuration.
one dynamic way to do it is the Bootstrap Router (BSR) mechanism [11] โจ one router in each PIM domain gets elected as the BSR through a simple election process. all the routers in the domain that are set up as RP candidates periodically unicast their candidacy to the BSR ๐ from those candidates, the BSR picks an RP-set and announces it in a Bootstrap message on a regular basis. Bootstrap messages get flooded hop-by-hop throughout the domain until every router in the domain knows the RP-Set ๐ซถ
to map a group to an RP, a router hashes the group address into the RP-set using an order-preserving hash function (one that minimizes changes when the RP-Set changes) ๐ง the RP that comes out of that is the one the router uses for that group ๐
4. Protocol Specification ๐
the PIM-SM spec is split into several parts, and honestly it's giving very organized ๐๏ธโจ
- Section 4.1 details the protocol state stored.
- Section 4.2 specifies the data packet forwarding rules.
- Section 4.3 specifies Designated Router (DR) election and the rules for sending and processing Hello messages.
- Section 4.4 specifies the PIM Register generation and processing rules.
- Section 4.5 specifies the PIM Join/Prune generation and processing rules.
- Section 4.6 specifies the PIM Assert generation and processing rules.
- Section 4.7 specifies the RP discovery mechanisms.
- Section 4.8 describes PIM-SSM, the subset of PIM required to support Source-Specific Multicast.
- Section 4.9 specifies the PIM packet formats.
- Section 4.10 provides a summary of PIM-SM timers, and Section 4.11 provides their default values.
4.1. PIM Protocol State ๐ธ
this section specifies all the protocol state a PIM implementation should maintain to work correctly ๐ซถ we call this state the Tree Information Base (TIB), because it holds the state of all the multicast distribution trees at this router. in this spec, PIM mechanisms are defined in terms of the TIB ๐ง but honestly only a very simple implementation would actually do packet forwarding directly from this state. most implementations will use it to build a multicast forwarding table, which then gets updated whenever the relevant TIB state changes โจ
even though we specify the state precisely, that doesn't mean an implementation of PIM-SM has to store it in this form ๐ this is an abstract state definition, needed to specify the router's behavior. a PIM-SM implementation is free to hold whatever internal state it needs and still be conformant with this specification, as long as it produces the same externally visible protocol behavior as an abstract router holding the following state ๐ฆ
we split TIB state into three sections ๐
(*,G)state: State that maintains the RP tree forG.(S,G)state: State that maintains a source-specific tree for sourceSand groupG.(S,G,rpt)state: State that maintains source-specific information about sourceSon the RP tree forG. For example, if a source is being received on the source-specific tree, it will normally have been pruned off the RP tree. This prune state is(S,G,rpt)state.
the state that should be kept is described below ๐ implementations will only maintain state when it's relevant to forwarding operations. for example, the "NoInfo" state might be assumed from the lack of other state information instead of being held explicitly ๐
4.1.1. General-Purpose State ๐
a router holds the following non-group-specific state ๐
For each interface:
- Effective Override Interval
- Effective Propagation Delay
- Suppression state: One of {
"Enable","Disable"} - Neighbor State:
- For each neighbor:
- Information from neighbor's Hello
- Neighbor's GenID.
- Neighbor Liveness Timer (NLT)
- For each neighbor:
- Designated Router (DR) State:
- Designated Router's IP Address
- DR's DR Priority
The Effective Override Interval, the Effective Propagation Delay, and the Interface suppression state are described in Section 4.3.3 โจ Designated Router state is described in Section 4.3 ๐ซถ
4.1.2. (*,G) State โจ
okay bestie, for every group G, a router keeps this state:
(*,G) state:
For each interface:
Local Membership:
State: One of {"NoInfo", "Include"}
PIM (*,G) Join/Prune State:
o State: One of {"NoInfo" (NI), "Join" (J),
"Prune-Pending" (PP)}
o Prune-Pending Timer (PPT)
o Join/Prune Expiry Timer (ET)
(*,G) Assert Winner State
o State: One of {"NoInfo" (NI), "I lost Assert" (L),
"I won Assert" (W)}
o Assert Timer (AT)
o Assert winner's IP Address (AssertWinner)
o Assert winner's Assert Metric (AssertWinnerMetric)
Not interface specific:
Upstream (*,G) Join/Prune State:
o State: One of {"NotJoined(*,G)", "Joined(*,G)"}
o Upstream Join/Prune Timer (JT)
o Last RP Used
o Last RPF Neighbor towards RP that was used
Local membership is what the local membership mechanism (like IGMP or MLD) reports on that interface, and it's giving very "who's in the group" energy ๐. You don't need to keep it if you aren't the DR on that interface, unless you won a (*,G) assert there for this group. Implementations are allowed to keep it anyway, in case they become the DR or assert winner later. It is RECOMMENDED to store this info if you can, because it cuts down on latency when things settle back into stable operation after a failure changes the DR ๐
. This info gets used by the pim_include(*,G) macro in Section 4.1.5.
PIM (,G) Join/Prune state comes from receiving PIM (,G) Join/Prune messages on that interface, and it's specified in Section 4.5.1 ๐ธ. It feeds the macros that work out the outgoing interface list in Section 4.1.5, and the JoinDesired(,G) macro in Section 4.5.4, which decides whether a Join(,G) should go upstream.
The (,G) Assert Winner state is what you get from sending or receiving (,G) Assert messages on that interface. It's specified in Section 4.6.2 ๐ง.
The upstream (,G) Join/Prune State mirrors the upstream (,G) state machine from Section 4.5.4, so it's basically the receipts ๐งพ.
The upstream (,G) Join/Prune Timer is what sends out the periodic Join(,G) messages, and it also lets you override Prune(*,G) messages from peers on an upstream LAN interface ๐ซถ.
The last RP used must be stored, because if the RP changes, the state for the groups whose RP changed has to be torn down and rebuilt ๐.
The last RPF neighbor toward the RP gets stored because if the MRIB changes, the RPF neighbor toward the RP might change too. When that happens, you need to send a new Join(,G) to the new upstream neighbor and a Prune(,G) to the old one. Similarly, if a router notices through a changed GenID in a Hello message that the upstream neighbor toward the RP has rebooted, it SHOULD re-instantiate state by sending a Join(*,G). These mechanisms are specified in Section 4.5.4 โจ.
4.1.3. (S,G) State ๐
okay so for every source/group pair (S,G), a router keeps this state:
(S,G) state:
For each interface:
Local Membership:
State: One of {"NoInfo", "Include"}
PIM (S,G) Join/Prune State:
o State: One of {"NoInfo" (NI), "Join" (J),
"Prune-Pending" (PP)}
o Prune-Pending Timer (PPT)
o Join/Prune Expiry Timer (ET)
(S,G) Assert Winner State
o State: One of {"NoInfo" (NI), "I lost Assert" (L),
"I won Assert" (W)}
o Assert Timer (AT)
o Assert winner's IP Address (AssertWinner)
o Assert winner's Assert Metric (AssertWinnerMetric)
Not interface specific:
Upstream (S,G) Join/Prune State:
o State: One of {"NotJoined(S,G)", "Joined(S,G)"}
o Upstream (S,G) Join/Prune Timer (JT)
o Last RPF Neighbor towards S that was used
o SPTbit (indicates (S,G) state is active)
o (S,G) Keepalive Timer (KAT)
Additional (S,G) state at the DR:
o Register state: One of {"Join" (J), "Prune" (P),
"Join-Pending" (JP), "NoInfo" (NI)}
o Register-Stop Timer (RST)
Local membership is what the local source-specific membership mechanism (like IGMP Version 3) reports on that interface, saying this particular source should be included. What's stored here is the result after any IGMPv3 inconsistencies have been sorted out ๐งน. You don't need to keep it if you aren't the DR on that interface, unless you won an (S,G) assert there for this group. Still, it is RECOMMENDED to store it if possible, since it shortens the latency to stable operation after a failure changes the DR ๐. This info gets used by the pim_include(S,G) macro in Section 4.1.5.
PIM (S,G) Join/Prune state comes from receiving PIM (S,G) Join/Prune messages on that interface, specified in Section 4.5.2 ๐ฆ. It feeds the outgoing interface list macros in Section 4.1.5, plus the JoinDesired(S,G) macro in Section 4.5.5, which decides whether a Join(S,G) goes upstream.
The (S,G) Assert Winner state is the result of sending or receiving (S,G) Assert messages on that interface, specified in Section 4.6.1 ๐.
The upstream (S,G) Join/Prune State follows the upstream (S,G) state machine in Section 4.5.5 โญ.
The upstream (S,G) Join/Prune Timer sends out the periodic Join(S,G) messages, and it also overrides Prune(S,G) messages from peers on an upstream LAN interface ๐ซถ.
The last RPF neighbor toward S gets stored because if the MRIB changes, the RPF neighbor toward S might change. If it does, you need to trigger a new Join(S,G) to the new upstream neighbor and a Prune(S,G) to the old one. Also, if the router sees a changed GenID in a Hello message showing that the upstream neighbor toward S has rebooted, it SHOULD re-instantiate state by sending a Join(S,G). These mechanisms are specified in Section 4.5.5 ๐ง.
The SPTbit tells you whether forwarding is happening on the (S,G) Shortest Path Tree (SPT) or on the (,G) tree. A router can hold (S,G) state and still be forwarding on (,G) state while the source-specific tree is being built, so don't panic ๐. When the SPTbit is FALSE, only (,G) forwarding state gets used to forward packets from S to G. When the SPTbit is TRUE, both (,G) and (S,G) forwarding state are used.
The (S,G) Keepalive Timer gets refreshed by data forwarded on this (S,G) forwarding state. It keeps (S,G) state alive when there are no explicit (S,G) Joins. This matters for the so-called "turnaround rules", which are when the RP uses (S,G) joins to stop encapsulation, and then (S,G) prunes to keep traffic from reaching the RP for no reason ๐ฅ.
On a DR, the (S,G) Register State tracks whether data should be encapsulated to the RP on the Register Tunnel. The (S,G) Register-Stop Timer tracks how long it is before encapsulation starts again for a given (S,G) ๐.
4.1.4. (S,G,rpt) State ๐ฆ
for every source/group pair (S,G) where a router also has (*,G) state, it keeps this extra state too:
(S,G,rpt) state:
For each interface:
Local Membership:
State: One of {"NoInfo", "Exclude"}
PIM (S,G,rpt) Join/Prune State:
o State: One of {"NoInfo", "Pruned",
"Prune-Pending"}
o Prune-Pending Timer (PPT)
o Join/Prune Expiry Timer (ET)
Not interface specific:
Upstream (S,G,rpt) Join/Prune State:
o State: One of {"RPTNotJoined(G)",
"NotPruned(S,G,rpt)", "Pruned(S,G,rpt)"}
o Override Timer (OT)
Local membership is what the local source-specific membership mechanism (like IGMPv3) reports on that interface, saying that even though there's (,G) Include state, this particular source should be excluded ๐
โโ๏ธ. Again, what gets stored is the result after any IGMPv3 inconsistencies between LAN members are resolved. You don't need to keep it if you aren't the DR on that interface, unless you won a (,G) assert there for this group. We RECOMMEND storing it if possible, because it cuts down latency when things settle after a DR change ๐. This info gets used by the pim_exclude(S,G) macro in Section 4.1.5.
PIM (S,G,rpt) Join/Prune state comes from receiving PIM (S,G,rpt) Join/Prune messages on that interface, specified in Section 4.5.3 ๐ท. It feeds the outgoing interface list macros in Section 4.1.5, and the rules for adding Prune(S,G,rpt) messages to Join(*,G) messages in Section 4.5.6.
The upstream (S,G,rpt) Join/Prune state works with the Override Timer to send the right override messages in response to Join/Prune messages from upstream peers on a LAN. That state and its behavior are specified in Section 4.5.7 ๐.
4.1.5. State Summarization Macros โจ
okay bestie, now we get the macros ๐ we're gonna use these to describe the state machines and pseudocode in the sections coming up ๐
The most important macros are the ones that define the outgoing interface list, aka the "olist", for whatever state you're looking at ๐ An olist can be "immediate" if it gets built straight from the state of that one type. Like, immediate_olist(S,G) is the olist you'd get if the router only had (S,G) state and no (*,G) or (S,G,rpt) state ๐ฆ The "inherited" olist pulls in state from other types. inherited_olist(S,G) is the one that matters for forwarding a packet from S to G when you're using both source-specific and group-specific state ๐ซถ
There is no immediate_olist(S,G,rpt), because (S,G,rpt) state is negative state ๐ It removes interfaces in the (*,G) olist from the olist that's actually used to forward traffic. So inherited_olist(S,G,rpt) is the olist used for a packet from S to G forwarding on the RP tree, and it's a strict subset of immediate_olist(*,G) ๐
Generally speaking, the inherited_olists are used for forwarding, and the immediate_olists are used to make decisions about state maintenance โจ
immediate_olist(*,G) =
joins(*,G) (+) pim_include(*,G) (-) lost_assert(*,G)
immediate_olist(S,G) =
joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G)
inherited_olist(S,G,rpt) =
( joins(*,G) (-) prunes(S,G,rpt) )
(+) ( pim_include(*,G) (-) pim_exclude(S,G))
(-) ( lost_assert(*,G) (+) lost_assert(S,G,rpt) )
inherited_olist(S,G) =
inherited_olist(S,G,rpt) (+)
joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G)
The macros pim_include(*,G) and pim_include(S,G) tell you which interfaces traffic might get forwarded to because there are hosts that are local members on that interface ๐ธ Normally only the DR cares about local membership ๐ But when an assert happens, the assert winner takes over the job of forwarding traffic to local members who asked for traffic on a group or a source/group pair ๐ง
pim_include(*,G) =
{ all interfaces I such that:
( ( I_am_DR( I ) AND lost_assert(*,G,I) == FALSE )
OR AssertWinner(*,G,I) == me )
AND local_receiver_include(*,G,I) }
pim_include(S,G) =
{ all interfaces I such that:
( (I_am_DR( I ) AND lost_assert(S,G,I) == FALSE )
OR AssertWinner(S,G,I) == me )
AND local_receiver_include(S,G,I) }
pim_exclude(S,G) =
{ all interfaces I such that:
( (I_am_DR( I ) AND lost_assert(*,G,I) == FALSE )
OR AssertWinner(*,G,I) == me )
AND local_receiver_exclude(S,G,I) }
The clause local_receiver_include(S,G,I) is true when the IGMP/MLD module (or some other local membership mechanism) has figured out that local members on interface I want to receive traffic sent specifically by S to G ๐ local_receiver_include(*,G,I) is true when that same module has figured out that local members on interface I want to receive all traffic sent to G, possibly skipping traffic from a specific set of sources ๐ And local_receiver_exclude(S,G,I) is true when local_receiver_include(*,G,I) is true but none of the local members want traffic from S ๐
The set joins(*,G) is every interface where the router has received (*,G) Joins ๐ฆ
joins(*,G) =
{ all interfaces I such that
DownstreamJPState(*,G,I) is either Join or Prune-Pending }
DownstreamJPState(*,G,I) is the state of the finite state machine in Section 4.5.1 ๐ซถ
The set joins(S,G) is every interface where the router has received (S,G) Joins โจ
joins(S,G) =
{ all interfaces I such that
DownstreamJPState(S,G,I) is either Join or Prune-Pending }
DownstreamJPState(S,G,I) is the state of the finite state machine in Section 4.5.2 ๐
The set prunes(S,G,rpt) is every interface where the router has received (*,G) joins and (S,G,rpt) prunes ๐
prunes(S,G,rpt) =
{ all interfaces I such that
DownstreamJPState(S,G,rpt,I) is Prune or PruneTmp }
DownstreamJPState(S,G,rpt,I) is the state of the finite state machine in Section 4.5.3 ๐
The set lost_assert(*,G) is every interface where the router has received (,G) joins but has lost a (,G) assert ๐ญ The macro lost_assert(*,G,I) is defined in Section 4.6.5.
lost_assert(*,G) =
{ all interfaces I such that
lost_assert(*,G,I) == TRUE }
The set lost_assert(S,G,rpt) is every interface where the router has received (*,G) joins but has lost an (S,G) assert ๐ The macro lost_assert(S,G,rpt,I) is defined in Section 4.6.5.
lost_assert(S,G,rpt) =
{ all interfaces I such that
lost_assert(S,G,rpt,I) == TRUE }
The set lost_assert(S,G) is every interface where the router has received (S,G) joins but has lost an (S,G) assert โญ The macro lost_assert(S,G,I) is defined in Section 4.6.5.
lost_assert(S,G) =
{ all interfaces I such that
lost_assert(S,G,I) == TRUE }
Next up are the pseudocode macros that get used all over the spec. Basically, RPF' is the RPF neighbor towards an RP or source, unless a PIM-Assert has overridden the normal choice of neighbor ๐ฅ
neighbor RPF'(*,G) {
if ( I_Am_Assert_Loser(*, G, RPF_interface(RP(G))) ) {
return AssertWinner(*, G, RPF_interface(RP(G)) )
} else {
return NBR( RPF_interface(RP(G)), MRIB.next_hop( RP(G) ) )
}
}
neighbor RPF'(S,G,rpt) {
if( I_Am_Assert_Loser(S, G, RPF_interface(RP(G)) ) ) {
return AssertWinner(S, G, RPF_interface(RP(G)) )
} else {
return RPF'(*,G)
}
}
neighbor RPF'(S,G) {
if ( I_Am_Assert_Loser(S, G, RPF_interface(S) )) {
return AssertWinner(S, G, RPF_interface(S) )
} else {
return NBR( RPF_interface(S), MRIB.next_hop( S ) )
}
}
RPF'(*,G) and RPF'(S,G) tell you which neighbor data packets should be coming from, and where joins should be sent, on the RP tree and the SPT respectively ๐
RPF'(S,G,rpt) is basically RPF'(*,G) modified by the result of an Assert(S,G) on RPF_interface(RP(G)) ๐ฆ In that case, packets from S are coming from a different router than RPF'(*,G) would give you ๐ If we only have active (,G) Join state, we need to accept packets from RPF'(S,G,rpt) and add a Prune(S,G,rpt) to the periodic Join(,G) messages that we send to RPF'(*,G) (see Section 4.5.6) ๐
The function MRIB.next_hop( S ) returns an address of the next-hop PIM neighbor toward the host S, based on the current MRIB ๐ธ If S is directly adjacent, then MRIB.next_hop( S ) returns NULL. At the RP for G, MRIB.next_hop( RP(G)) returns NULL ๐ง
The function NBR( I, A ) uses info gathered from PIM Hello messages to map the IP address A of a directly connected PIM neighbor router on interface I to the primary IP address of that same router (Section 4.3.4) ๐ซถ The primary IP address of a neighbor is the address it uses as the source of its PIM Hello messages ๐ A neighbor's IP address might not be unique inside the PIM neighbor database because of scope issues ๐ The address must, however, be unique amongst the addresses of all the PIM neighbors on a specific interface โจ
I_Am_Assert_Loser(S, G, I) is true if the Assert state machine (in Section 4.6.1) for (S,G) on Interface I is in the "I am Assert Loser" state ๐
I_Am_Assert_Loser(*, G, I) is true if the Assert state machine (in Section 4.6.2) for (*,G) on Interface I is in the "I am Assert Loser" state ๐
4.2. Data Packet Forwarding Rules ๐
okay bestie, this is the forwarding section and it's the main event โจ PIM-SM's packet forwarding rules are written as pseudocode, so here are the variables you'll see:
iifis the incoming interface of the packet.Sis the source address of the packet.Gis the destination address of the packet (the group address).RPis the address of the Rendezvous Point for this group.RPF_interface(S)is the interface the MRIB says would be used to route packets toS.RPF_interface(RP)is the interface the MRIB says would be used to route packets to the RP. The exception is at the RP itself when it's the decapsulation interface, which is the "virtual" interface where Register packets come in ๐ฆ
Here's the flow, bestie. First, we restart (or start) the Keepalive Timer if the source is on a directly connected subnet. Second, we check whether the SPTbit should be set, because we've just switched from the RP tree to the SPT. Next, we check whether the packet should be accepted based on TIB state and the interface it arrived on.
If the packet should be forwarded using (S,G) state, we build an outgoing interface list for it. If that list isn't empty, we restart the (S,G) state Keepalive Timer. If it should be forwarded using (*,G) state, we just build an outgoing interface list, and we also check whether we should kick off a switch to receive this source on a shortest path tree. Finally, we remove the incoming interface from the list we made, and if what's left isn't empty, we forward the packet out of those interfaces ๐ธ
Here's the pseudocode, exactly as written:
On receipt of data from S to G on interface iif:
if( DirectlyConnected(S) == TRUE AND iif == RPF_interface(S) ) {
set KeepaliveTimer(S,G) to Keepalive_Period
# Note: A register state transition or UpstreamJPState(S,G)
# transition may happen as a result of restarting
# KeepaliveTimer, and must be dealt with here.
}
if( iif == RPF_interface(S) AND UpstreamJPState(S,G) == Joined AND
inherited_olist(S,G) != NULL ) {
set KeepaliveTimer(S,G) to Keepalive_Period
}
Update_SPTbit(S,G,iif)
oiflist = NULL
if( iif == RPF_interface(S) AND SPTbit(S,G) == TRUE ) {
oiflist = inherited_olist(S,G)
} else if( iif == RPF_interface(RP(G)) AND SPTbit(S,G) == FALSE ) {
oiflist = inherited_olist(S,G,rpt)
CheckSwitchToSpt(S,G)
} else {
# Note: RPF check failed.
# A transition in an Assert finite state machine may cause an
# Assert(S,G) or Assert(*,G) message to be sent out interface iif.
# See Section 4.6 for details.
if ( SPTbit(S,G) == TRUE AND iif is in inherited_olist(S,G) ) {
send Assert(S,G) on iif
} else if ( SPTbit(S,G) == FALSE AND
iif is in inherited_olist(S,G,rpt) ) {
send Assert(*,G) on iif
}
}
oiflist = oiflist (-) iif
forward packet on all interfaces in oiflist
Some more macros you'll need, bestie ๐ง
DirectlyConnected(S)is TRUE if the sourceSis on any subnet directly connected to this router (or if the packet originates on this router).inherited_olist(S,G)andinherited_olist(S,G,rpt)are defined in Section 4.1.inherited_olist(S,G)is basically the outgoing interface list for packets forwarded on (S,G) state, taking (*,G) state, asserts, and so on into account ๐inherited_olist(S,G,rpt)is the outgoing interface list for packets forwarded on (*,G) state, taking (S,G,rpt) prune state, asserts, and so on into account.Update_SPTbit(S,G,iif)is defined in Section 4.2.2.CheckSwitchToSpt(S,G)is defined in Section 4.2.1.UpstreamJPState(S,G)is the state of the finite state machine in Section 4.5.5.Keepalive_Periodis defined in Section 4.11.
Data-triggered PIM-Assert messages sent from the forwarding code above SHOULD be rate-limited, in an implementation-dependent way. ๐
4.2.1. Last-Hop Switchover to the SPT โก
okay so in Sparse-Mode PIM, last-hop routers join the shared tree towards the RP ๐ Once traffic from sources to joined groups shows up at a last-hop router, it gets to choose whether to switch and receive that traffic on a shortest path tree (SPT). It's giving choice!
The decision to switch to the SPT is controlled like this:
void
CheckSwitchToSpt(S,G) {
if ( ( pim_include(*,G) (-) pim_exclude(S,G)
(+) pim_include(S,G) != NULL )
AND SwitchToSptDesired(S,G) ) {
# Note: Restarting the KAT will result in the SPT switch.
set KeepaliveTimer(S,G) to Keepalive_Period
}
}
SwitchToSptDesired(S,G) is a policy function, and it's implementation defined ๐ An "infinite threshold" policy can be built by making SwitchToSptDesired(S,G) return false all the time. A "switch on first packet" policy can be built by making it return true once a single packet has been received for that source and group. Pick your vibe โจ
4.2.2. Setting and Clearing the (S,G) SPTbit ๐
The (S,G) SPTbit is what tells a router whether to forward on (,G) state or on (S,G) state. When switching from the RP tree to the source tree, there's a transition period. Data is still arriving because of upstream (,G) state while upstream (S,G) state is still being set up, and during that time a router should keep forwarding only on (*,G) state. This avoids temporary black holes, which would happen if a Prune(S,G,rpt) got sent before the upstream (S,G) state was done being established. Protect the bag, bestie ๐ซถ
So when a packet arrives, the (S,G) SPTbit gets updated like this:
void
Update_SPTbit(S,G,iif) {
if ( iif == RPF_interface(S)
AND JoinDesired(S,G) == TRUE
AND ( DirectlyConnected(S) == TRUE
OR RPF_interface(S) != RPF_interface(RP(G))
OR inherited_olist(S,G,rpt) == NULL
OR ( ( RPF'(S,G) == RPF'(*,G) ) AND
( RPF'(S,G) != NULL ) )
OR ( I_Am_Assert_Loser(S,G,iif) ) ) ) {
Set SPTbit(S,G) to TRUE
}
}
A router can also set SPTbit(S,G) to TRUE in other cases, like when it receives an Assert(S,G) on RPF_interface(S) (see Section 4.6.1). Also, JoinDesired(S,G) is defined in Section 4.5.5 and tells us whether we have the right (S,G) Join state to send a Join(S,G) upstream ๐ธ
Basically, Update_SPTbit(S,G,iif) sets the SPTbit when we have the right (S,G) join state, the packet came in on the correct upstream interface for S, and one or more of these conditions apply:
- The source is directly connected, so switching to the SPT is a no-op.
- The RPF interface to S is different from the RPF interface to the RP. The packet arrived on
RPF_interface(S), so the SPT must already be complete. - Nobody wants the packet on the RP tree.
RPF'(S,G) == RPF'(*,G). Here the router can never tell whether the SPT is done, so it should just switch immediately. TheRPF'(S,G) != NULLcheck makes sure the SPTbit only gets set if the RPF neighbor towards S is valid ๐ฆ
When the RPF interface is the same for the RP and for S, but RPF'(S,G) and RPF'(*,G) differ, we wait for an Assert(S,G). That Assert says the upstream router with (S,G) state believes the SPT is complete. Item (3) above is still needed, though, because there might not be any (*,G) state around to trigger an Assert(S,G) at all ๐
The SPTbit gets cleared in the (S,G) upstream state machine (see Section 4.5.5) when JoinDesired(S,G) becomes FALSE.
4.3. Designated Routers (DRs) and Hello Messages ๐
A shared-media LAN like Ethernet can have several PIM-SM routers connected to it. Only one of them, the DR, acts on behalf of directly connected hosts for the PIM-SM protocol ๐ The line between LANs and point-to-point interfaces can get kinda blurry, and routers can also have multicast host functionality, so the PIM-SM specification doesn't make any distinction between the two. That means DR election happens on all interfaces, LAN or not โจ
DR election is done with Hello messages. Hello messages are also how option negotiation happens in PIM, so extra functionality can be turned on or parameters can be tuned ๐
4.3.1. Sending Hello Messages โจ
okay so PIM Hello messages get sent periodically on every PIM-enabled interface ๐ They let a router find out who its PIM neighbors are on each interface ๐ Hellos are also how a DR gets elected and how routers negotiate extra capabilities ๐ซถ A router needs to record the Hello info it gets from each PIM neighbor, no skipping ๐
Hello messages MUST be sent on all active interfaces, including physical point-to-point links, and they're multicast to the 'ALL-PIM-ROUTERS' group address ('224.0.0.13' for IPv4 and 'ff02::d' for IPv6) ๐ธ
ngl some implementations don't send Hellos on point-to-point interfaces ๐ญ that's non-compliant behavior. A compliant PIM router MUST send Hello messages, even on point-to-point interfaces ๐
Each interface has its own Hello Timer (HT(I)) that triggers Hello sends ๐ง When PIM gets enabled on an interface, or a router first starts up, that timer gets set to a random value between 0 and Triggered_Hello_Delay. This stops everyone's Hellos from syncing up if a bunch of routers power on at the same time ๐ After that first randomized wait, Hello messages MUST be sent every Hello_Period seconds. The Hello Timer SHOULD NOT be reset except when it expires ๐
Neighbors won't accept Join/Prune or Assert messages from a router until they've heard a Hello from that router first ๐ซ So if a router needs to send a Join/Prune or Assert on an interface where it hasn't yet sent a Hello using its currently configured IP address, it MUST immediately send that Hello without waiting for the Hello Timer to expire, followed by the Join/Prune or Assert message ๐ฅ
The DR Priority option lets an admin give a router a better shot at being DR by setting a numerically larger DR Priority ๐ The DR Priority option SHOULD be included in every Hello message, even if no DR Priority is explicitly configured on that interface. That's needed because priority-based DR election only turns on when all neighbors on an interface say they can use the DR Priority option ๐ง The default priority is 1.
The Generation Identifier (GenID) option SHOULD be included in all Hello messages ๐ It holds a randomly generated 32-bit value that gets regenerated every time PIM forwarding starts or restarts on the interface, including when the router itself restarts. When a Hello with a new GenID arrives from a neighbor, any old Hello info about that neighbor SHOULD be discarded and replaced by the info from the new Hello. This can cause a new DR to be chosen on that interface ๐
The LAN Prune Delay option SHOULD be included in all Hello messages sent on multi-access LANs โฑ๏ธ It advertises a router's ability to use values other than the defaults for Propagation_Delay and Override_Interval, which affect the Prune-Pending, Upstream Join, and Override Timers (defined in Section 4.10).
The Address List option advertises all the secondary addresses associated with the source interface of the router that sent the message ๐๏ธ The option MUST be included in all Hello messages if there are secondary addresses associated with the source interface, and MAY be omitted if no secondary addresses exist.
So new or rebooting routers can find their neighbors fast ๐ When a Hello comes in from a new neighbor, or a Hello with a new GenID comes from an existing one, a new Hello SHOULD be sent on this interface after a randomized delay between 0 and Triggered_Hello_Delay. This triggered message doesn't need to change the timing of the scheduled periodic message. If a router needs to send a Join/Prune to the new neighbor, or send an Assert in response to an Assert from the new neighbor, before that randomized delay is up, it MUST immediately send the relevant Hello without waiting for the Hello Timer to expire, followed by the Join/Prune or Assert message. If it doesn't do this, the new neighbor will discard the Join/Prune or Assert message ๐ โโ๏ธ
Before an interface goes down or changes its primary IP address, a Hello with a zero HoldTime SHOULD be sent right away (with the old IP address if the IP address changed) ๐ That makes PIM neighbors remove this neighbor (or its old IP address) immediately. After an interface has changed its IP address, it MUST send a Hello with its new IP address ๐ If an interface changes one of its secondary IP addresses, a Hello with an updated Address List option and a non-zero HoldTime SHOULD be sent right away ๐ so neighbors update that neighbor's list of secondary addresses immediately.
4.3.2. DR Election
When a PIM Hello comes in on interface I, the following info about the sending neighbor gets recorded ๐
neighbor.interface: the interface the Hello arrived on.neighbor.primary_ip_address: the IP address the PIM neighbor used as the source address of the Hello.neighbor.genid: the Generation ID of the PIM neighbor.neighbor.dr_priority: the DR Priority field of the PIM neighbor, if it's present in the Hello.neighbor.dr_priority_present: a flag that says whether the DR Priority field was in the Hello.neighbor.timeout: a timer value that times out the neighbor state when it goes stale, also known as the Neighbor Liveness Timer.
The Neighbor Liveness Timer (NLT(N,I)) gets reset to Hello_Holdtime (from the Hello Holdtime option) whenever a Hello containing a Holdtime option arrives, or to Default_Hello_Holdtime if the Hello doesn't contain the Holdtime option โณ Neighbor state gets deleted when the neighbor timeout expires ๐
The function for computing the DR on interface I is:
host
DR(I) {
dr = me
for each neighbor on interface I {
if ( dr_is_better( neighbor, dr, I ) == TRUE ) {
dr = neighbor
}
}
return dr
}
The function used for comparing DR "metrics" on interface I is:
bool
dr_is_better(a,b,I) {
if( there is a neighbor n on I for which n.dr_priority_present
is false ) {
return a.primary_ip_address > b.primary_ip_address
} else {
return ( a.dr_priority > b.dr_priority ) OR
( a.dr_priority == b.dr_priority AND
a.primary_ip_address > b.primary_ip_address )
}
}
The trivial function I_am_DR(I) is defined to help readability ๐ง
bool
I_am_DR(I) {
return DR(I) == me
}
The DR Priority is a 32-bit unsigned number, and the numerically larger priority is always preferred ๐ A router's idea of the current DR on an interface can change when a PIM Hello comes in, when a neighbor times out, or when a router's own DR Priority changes ๐ If the router becomes the DR or stops being the DR, that normally makes the DR Register state machine change state ๐งฉ Subsequent actions are decided by that state machine.
We note that some PIM implementations don't send Hello messages on point-to-point interfaces, so they can't perform DR election on those interfaces. That's non-compliant behavior. DR election MUST be performed on ALL active PIM-SM interfaces ๐
4.3.3. Reducing Prune Propagation Delay on LANs โจ
Okay so, on top of the DR Election info, we also grab these per-neighbor bits from the LAN Prune Delay Hello option ๐
neighbor.lan_prune_delay_present: A flag indicating if the LAN Prune Delay option was present in the Hello message.neighbor.tracking_support: A flag storing the value of the T bit in the LAN Prune Delay option if it is present in the Hello message. This indicates the neighbor's capability to disable Join message suppression.neighbor.propagation_delay: The Propagation Delay field of the LAN Prune Delay option (if present) in the Hello message.neighbor.override_interval: The Override_Interval field of the LAN Prune Delay option (if present) in the Hello message.
All that extra state gets deleted along with the DR neighbor state when the neighbor timeout expires ๐
Just like the DR Priority option, the LAN Prune Delay info is not used unless every neighbor on a link advertises the option. Main character rule ๐ฆ Here's the function that computes this:
bool
lan_delay_enabled(I) {
for each neighbor on interface I {
if ( neighbor.lan_prune_delay_present == false ) {
return false
}
}
return true
}
The Propagation Delay a router puts in the LAN Prune Delay option is its way of saying how long messages take to travel across the link ๐ธ It's used by upstream routers to figure out how long to wait for a Join override message before pruning an interface. This value SHOULD be configurable by the system administrator.
PIM implementers SHOULD enforce a lower bound on the permitted values for this delay, to leave room for scheduling and processing delays inside the router. ngl, those delays are real: received messages can get processed late, and triggered messages can get sent late too ๐ญ If the Propagation Delay is set too low, a downstream router might not be able to override a neighbor's Prune message in time, and the upstream neighbor stops forwarding. Temporary forwarding outage, not it ๐
When all routers on a link can negotiate a Propagation Delay that's different from the default, the largest value that any neighbor advertises wins ๐ Here's the function for the Effective Propagation Delay of interface I:
time_interval
Effective_Propagation_Delay(I) {
if ( lan_delay_enabled(I) == false ) {
return Propagation_delay_default
}
delay = Propagation_Delay(I)
for each neighbor on interface I {
if ( neighbor.propagation_delay > delay ) {
delay = neighbor.propagation_delay
}
}
return delay
}
When several downstream routers share a multi-access link, they could all send override messages at the exact same moment and that's chaos. So the sending of those messages gets delayed by a small random amount of time ๐ฒ The randomization period should represent the size of the PIM router population on the link. Each router shares its view of how much randomization it needs in the Override Interval field of the LAN Prune Delay option ๐ง
When all routers on a link can negotiate an Override Interval that's different from the default, the largest value any neighbor advertises is the one that's chosen. The function for the Effective Override Interval of interface I is:
time_interval
Effective_Override_Interval(I) {
if ( lan_delay_enabled(I) == false ) {
return t_override_default
}
delay = Override_Interval(I)
for each neighbor on interface I {
if ( neighbor.override_interval > delay ) {
delay = neighbor.override_interval
}
}
return delay
}
Now, the mechanisms for this aren't specified in this document, but it is possible for upstream routers to explicitly track the join membership of individual downstream routers if Join suppression is disabled ๐ A router says it's willing to disable Join suppression by setting the T bit in the LAN Prune Delay Hello option. Unless every PIM router on a link negotiates this capability, explicit tracking and disabling Join suppression are not possible. The function for the state of Suppression on interface I is:
bool
Suppression_Enabled(I) {
if ( lan_delay_enabled(I) == false ) {
return true
}
for each neighbor on interface I {
if ( neighbor.tracking_support == false ) {
return true
}
}
return false
}
Note that the setting of Suppression_Enabled(I) affects the value of t_suppressed (see Section 4.11) ๐ซถ
4.3.4. Maintaining Secondary Address Lists ๐
Okay this one's giving mapping vibes ๐บ๏ธ A router tells its PIM neighbors about its interface secondary addresses so they have a way to map next_hop info from their MRIB to a primary address they can use as a destination for Join/Prune messages. That mapping happens through the NBR macro. A neighbor's primary address comes from the source IP address in its PIM Hello messages. Secondary addresses ride along inside the Hello message, in an Address List Hello option. The primary address of the router's source interface MUST NOT be listed within the Address List Hello option. Don't be that router ๐ โโ๏ธ
In addition to the DR Election info, we also get this per-neighbor bit from the Address List Hello option:
neighbor.secondary_address_list: The list of secondary addresses used by the PIM neighbor on the interface through which the Hello message was transmitted.
When a received PIM Hello message has an Address List Hello option, the list of secondary addresses in that message completely replaces whatever secondary addresses were previously associated with that neighbor. Fresh start, bestie ๐ If a received PIM Hello message does not have an Address List Hello option, then all secondary addresses associated with the neighbor MUST be deleted. And if a received message has an Address List Hello option that includes the sending router's primary address in its list of secondary addresses (which isn't expected, so it's kind of a plot twist ๐ญ), then the addresses listed in the message, excluding the primary address, are used to update that neighbor's secondary addresses.
All the advertised secondary addresses in received Hello messages must be checked against the ones previously advertised by all other PIM neighbors on that interface. If there's a conflict, meaning the same secondary address was previously advertised by another neighbor, then only the most recently received mapping MUST be maintained, and an error message SHOULD be logged to the administrator in a rate-limited manner. Not spamming the admin, iykyk ๐
Last thing, and it's a big one ๐จ Within one Address List Hello option, all the addresses MUST be of the same address family. You are not allowed to mix IPv4 and IPv6 addresses in the same message, periodt. Also, the address family of the fields in the message SHOULD match the IP source and destination addresses of the packet header ๐
4.4. PIM Register Messages ๐ฌ
Okay so the Designated Router (DR) on a LAN or point-to-point link takes multicast packets from local sources and encapsulates them to the RP for the relevant group ๐ท It does this unless it recently received a Register-Stop message for that (S,G) or (*,G) from the RP. When the DR gets a Register-Stop message from the RP, it starts a Register-Stop Timer to keep track of this state. Just before that timer expires, the DR sends a Null-Register message to the RP, so the RP can refresh the Register-Stop info at the DR ๐ If the Register-Stop Timer actually expires, the DR goes back to encapsulating packets from the source to the RP. Back in action ๐ฌ
4.4.1. Sending Register Messages from the DR ๐ฆ
Every PIM-SM router has the capability to be a DR, which is so cute ๐ฅน The state machine below handles Register functionality. For the purposes of the spec, the mechanism that encapsulates packets to the RP is represented as a Register-Tunnel interface. That interface gets added to or removed from the (S,G) olist. Once it's there, the tunnel interface takes part in the normal packet forwarding rules from Section 4.2, just like everybody else ๐
If register state is maintained, it's maintained only for directly connected sources, and it's per-(S,G). The DR's per-(S,G) Register state machine has four states:
- Join (J): The register tunnel is "joined." (The join is actually implicit, but the DR acts as if the RP has joined the DR on the tunnel interface.)
- Prune (P): The register tunnel is "pruned." This happens when a Register-Stop is received.
- Join-Pending (JP): The register tunnel is pruned, but the DR is contemplating adding it back ๐ค
- NoInfo (NI): No information. This is the initial state, and it's also the state when the router is not the DR.
On top of that, a Register-Stop Timer (RST) is kept whenever the state machine is not in the NoInfo state โฑ๏ธ
Figure 1: Per-(S,G) Register State Machine at a DR
+----------++----------------------------------------------------------+
| || Event |
| ++----------+-----------+-----------+-----------+-----------+
|Prev State||Register- | Could | Could | Register- | RP changed|
| ||Stop Timer| Register | Register | Stop | |
| ||expires | ->True | ->False | received | |
+----------++----------+-----------+-----------+-----------+-----------+
|NoInfo ||- | -> J state| - | - | - |
|(NI) || | add reg | | | |
| || | tunnel | | | |
+----------++----------+-----------+-----------+-----------+-----------+
| ||- | - | -> NI | -> P state| -> J state|
| || | | state | | |
| || | | remove reg| remove reg| update reg|
|Join (J) || | | tunnel | tunnel; | tunnel |
| || | | | set | |
| || | | | Register- | |
| || | | | Stop | |
| || | | | Timer(*) | |
+----------++----------+-----------+-----------+-----------+-----------+
| ||-> J state| - | -> NI | -> P state| -> J state|
| || | | state | | |
|Join- ||add reg | | | set | add reg |
|Pending ||tunnel | | | Register- | tunnel; |
|(JP) || | | | Stop | cancel |
| || | | | Timer(*) | Register- |
| || | | | | Stop Timer|
+----------++----------+-----------+-----------+-----------+-----------+
| ||-> JP | - | -> NI | - | -> J state|
| ||state | | state | | |
| ||set | | | | add reg |
|Prune (P) ||Register- | | | | tunnel; |
| ||Stop | | | | cancel |
| ||Timer(**);| | | | Register- |
| ||send Null-| | | | Stop Timer|
| ||Register | | | | |
+----------++----------+-----------+-----------+-----------+-----------+
Notes:
(*) okay so the Register-Stop Timer gets set to a random value picked uniformly from the interval ( 0.5 * Register_Suppression_Time, 1.5 * Register_Suppression_Time), minus Register_Probe_Time. ๐
(**) the Register-Stop Timer is set to Register_Probe_Time. ๐ฆ
Subtracting Register_Probe_Time is a bit unnecessary, ngl, because it's really small next to Register_Suppression_Time. But this was in the old spec, so it stays in for compatibility. ๐
There are three actions defined here, and they're lowkey the heart of the whole thing โจ
Add Register Tunnel
A Register-Tunnel virtual interface, VI, gets created (if it doesn't already exist) with its encapsulation target set to RP(G). DownstreamJPState(S,G,VI) is set to Join state, which means the tunnel interface gets added to immediate_olist(S,G) and inherited_olist(S,G). ๐ธ
Remove Register Tunnel
VI is the Register-Tunnel virtual interface whose encapsulation target is RP(G). DownstreamJPState(S,G,VI) is set to NoInfo state, so the tunnel interface gets removed from immediate_olist(S,G) and inherited_olist(S,G). If DownstreamJPState(S,G,VI) is NoInfo for all (S,G), then VI can be deleted. ๐งน
Update Register Tunnel
This action happens when RP(G) changes. ๐
VI_old is the Register-Tunnel virtual interface with encapsulation target old_RP(G). A Register-Tunnel virtual interface, VI_new, gets created (if it doesn't already exist) with its encapsulation target set to new_RP(G). DownstreamJPState(S,G,VI_old) is set to NoInfo state, and DownstreamJPState(S,G,VI_new) is set to Join state. If DownstreamJPState(S,G,VI_old) is NoInfo for all (S,G), then VI_old can be deleted. ๐
Note that we can't just change the encapsulation target of VI_old, because not every group using that encapsulation tunnel will have moved to the same new RP. It's giving "not all of my friends are going to the same party" energy ๐ฅบ
CouldRegister(S,G)
The macro "CouldRegister" in the state machine is defined as:
bool CouldRegister(S,G) {
return ( I_am_DR( RPF_interface(S) ) AND
KeepaliveTimer(S,G) is running AND
DirectlyConnected(S) == TRUE )
}
Note that when the DR receives a packet from a directly connected source, KeepaliveTimer(S,G) needs to be set by the packet forwarding rules before CouldRegister(S,G) gets computed in the register state machine. Otherwise the first packet from a source won't be registered. Don't skip this part bestie ๐ญ
Encapsulating Data Packets in the Register Tunnel
Conceptually, the Register Tunnel is an interface with a smaller MTU than the underlying IP interface toward the RP. IP fragmentation on packets forwarded over the Register Tunnel is done based on this smaller MTU. The encapsulating DR may do Path MTU Discovery toward the RP to figure out the tunnel's effective MTU. Fragmentation for the smaller MTU should account for both the outer IP header and the PIM register header overhead. If a multicast packet gets fragmented on the way into the Register Tunnel, each fragment is encapsulated on its own, so it carries IP, PIM, and inner IP headers. ๐
In IPv6, the DR MUST perform Path MTU Discovery. An ICMP Packet Too Big message MUST be sent by the encapsulating DR if it receives a packet that won't fit in the tunnel's effective MTU. If the MTU between the DR and the RP makes the effective tunnel MTU smaller than 1280 (the IPv6 minimum MTU), the DR MUST send Fragmentation Required messages with an MTU value of 1280, and MUST fragment its PIM register messages as required, using an IPv6 fragmentation header between the outer IPv6 header and the PIM Register header. This part is serious, no cap ๐
The TTL of a forwarded data packet gets decremented before it's encapsulated in the Register Tunnel. The encapsulating packet uses the normal TTL the router would use for any locally generated IP packet. โญ
The IP Explicit Congestion Notification (ECN) bits should be copied from the original packet to the IP header of the encapsulating packet. They SHOULD NOT be set independently by the encapsulating router. Hands off the ECN bits, periodt ๐ โโ๏ธ
The Diffserv Code Point (DSCP) should be copied from the original packet to the IP header of the encapsulating packet. It MAY be set independently by the encapsulating router, based on static configuration or traffic classification. For more on setting the DSCP on tunnels, see [12]. ๐
Handling Register-Stop(*,G) Messages at the DR
An old RP might send a Register-Stop message with the source address set to all zeros. That was the normal move in RFC 2362 when a Register message matched (,G) state at the RP, and it was defined as meaning "stop encapsulating all sources for this group". The thing is, the behavior of such a Register-Stop(,G) is ambiguous or incorrect in some situations. ๐ค
We specify that an RP should not send Register-Stop(*,G) messages. But for compatibility, a DR should be able to accept one if it gets received. ๐ซถ
A Register-Stop(,G) should be treated as a Register-Stop(S,G) for every (S,G) Register state machine that isn't in the NoInfo state. A router should not apply a Register-Stop(,G) to sources that become active after the Register-Stop(*,G) was received. โจ
4.4.2. Receiving Register Messages at the RP ๐
When an RP receives a Register message, it decides what to do using the following pseudocode:
packet_arrives_on_rp_tunnel( pkt ) {
if( outer.dst is not one of my addresses ) {
drop the packet silently.
# Note: This may be a spoofing attempt.
}
if( I_am_RP(G) AND outer.dst == RP(G) ) {
sentRegisterStop = FALSE;
if ( SPTbit(S,G) OR
( SwitchToSptDesired(S,G) AND
( inherited_olist(S,G) == NULL ))) {
send Register-Stop(S,G) to outer.src
sentRegisterStop = TRUE;
}
if ( SPTbit(S,G) OR SwitchToSptDesired(S,G) ) {
if ( sentRegisterStop == TRUE ) {
set KeepaliveTimer(S,G) to RP_Keepalive_Period;
} else {
set KeepaliveTimer(S,G) to Keepalive_Period;
}
}
if( !SPTbit(S,G) AND ! pkt.NullRegisterBit ) {
decapsulate and forward the inner packet to
inherited_olist(S,G,rpt) # Note (+)
}
} else {
send Register-Stop(S,G) to outer.src
# Note (*)
}
}
outer.dst is the IP destination address of the encapsulating header.
outer.src is the IP source address of the encapsulating header, which is the DR's address.
I_am_RP(G) is true if the group-to-RP mapping says this router is the RP for the group. ๐บ๏ธ
Note (): this may block traffic from S for Register_Suppression_Time if the DR learned about a new group-to-RP mapping before the RP did. But that doesn't matter unless we figure out some way for the RP to also accept (,G) joins when it doesn't yet realize it's about to become the RP for G. Once the RP learns the new group-to-RP mapping, it all gets sorted out. We decided to do nothing about this and just accept that PIM may suffer interrupted (*,G) connectivity after an RP change. Sometimes you just have to let it go ๐ซ
Note (+): implementations SHOULD NOT make this a special case. Instead they SHOULD arrange for this path to rejoin the normal packet forwarding path. All of the appropriate actions from the "On receipt of data from S to G on interface iif" pseudocode in Section 4.2 should be performed. ๐
KeepaliveTimer(S,G) gets restarted at the RP when packets arrive on the proper tunnel interface and the RP wants to switch to the SPT, or when the SPTbit is already set. This may cause the upstream (S,G) state machine to trigger a join if inherited_olist(S,G) is not NULL. ๐
An RP should keep (S,G) state that was created in response to a Register message for at least ( 3 * Register_Suppression_Time ). Otherwise, the RP may stop joining (S,G) before the DR for S has restarted sending registers. Traffic would then be interrupted until the Register-Stop Timer expires at the DR. Nobody wants that drama ๐
So at the RP, KeepaliveTimer(S,G) should be restarted to ( 3 * Register_Suppression_Time + Register_Probe_Time ). โฐ
When forwarding a packet from the Register Tunnel, the TTL of the original data packet gets decremented after it's decapsulated. ๐
The IP ECN bits should be copied from the IP header of the Register packet to the decapsulated packet. ๐ท
The DSCP should be copied from the IP header of the Register packet to the decapsulated packet. The RP MAY keep the DSCP of the inner packet, or re-classify the packet and apply a different DSCP. The scenarios where each of these might be useful are discussed in [12]. ๐
4.5. PIM Join/Prune Messages ๐
A PIM Join/Prune message is basically a list of groups, plus a list of Joined and Pruned sources for each group. When a Join/Prune message is processed, each Joined or Pruned source for a group is effectively looked at on its own, and it applies to one or more of the following state machines. ๐งฉ
When the Join/Prune message's Upstream Neighbor Address field addresses this router, (,G) Joins and Prunes can affect both the (,G) and (S,G,rpt) downstream state machines. (S,G) and (S,G,rpt) Joins and Prunes can only affect their own respective downstream state machines. When the Upstream Neighbor Address field addresses a different router, most Join or Prune messages could affect each upstream state machine. ๐ฏ
In general, a PIM Join/Prune message should only be accepted for processing if it comes from a known PIM neighbor. A PIM router learns about its PIM neighbors through PIM Hello messages. If a router gets a Join/Prune message from a particular IP source address and it hasn't seen a PIM Hello message from that source address, then the Join/Prune message SHOULD be discarded without further processing. ๐ซ Also, if the Hello message from a neighbor was authenticated (see Section 6.3), then all Join/Prune messages from that neighbor MUST also be authenticated. ๐
We also know that some older PIM implementations incorrectly fail to send Hello messages on point-to-point interfaces. So we RECOMMEND that a configuration option be provided to allow interoperation with those older routers. That configuration option SHOULD NOT be enabled by default. Opt-in only, bestie ๐ฆ
4.5.1. Receiving (*,G) Join/Prune Messages โจ
okay so when a router gets a Join(*,G), it has to check first whether the RP in the message matches RP(G) (aka the router's own idea of who the RP is) ๐ง. If the RP doesn't match, that Join(*,G) should be silently dropped. ๐คซ (BUT other source list entries, like (S,G,rpt) or (S,G), in the same Group-Specific Set should still get processed, so don't throw the whole thing out ๐
โโ๏ธ.) If a router has no RP info (like it hasn't recently gotten a BSR message), it may choose to accept the Join(*,G) and treat the RP in the message as RP(G). ๐ซถ And Prune(*,G) messages get processed even when the RP doesn't match RP(G), which is so iconic of them ๐
.
Here's the per-interface state machine for receiving (*,G) Join/Prune messages. There are three states, and they are giving main character energy ๐:
- NoInfo (NI): the interface has no (*,G) Join state and no timers running. Totally empty vibes ๐ซง.
- Join (J): the interface has (,G) Join state, which means the router forwards packets destined for G out of this interface, *except if there's also (S,G,rpt) prune information (see Section 4.5.3) or the router lost an assert on this interface ๐ซ.
- Prune-Pending (PP): the router got a Prune(*,G) on this interface from a downstream neighbor and is waiting to see if that prune gets overridden by another downstream router ๐. For forwarding, Prune-Pending acts exactly like Join, no cap ๐งข.
The state machine also runs on two timers โฐ:
- Expiry Timer (ET): restarted every time a valid Join(*,G) comes in. When it expires, the interface goes back to NoInfo for this group ๐ค.
- Prune-Pending Timer (PPT): set when a valid Prune(*,G) is received. When it expires, the interface goes back to NoInfo for this group ๐.
Figure 2: Downstream Per-Interface (*,G) State Machine
+------------++--------------------------------------------------------+
| || Event |
| ++-------------+--------------+-------------+-------------+
|Prev State ||Receive | Receive | Prune- | Expiry Timer|
| ||Join(*,G) | Prune(*,G) | Pending | Expires |
| || | | Timer | |
| || | | Expires | |
+------------++-------------+--------------+-------------+-------------+
| ||-> J state | -> NI state | - | - |
|NoInfo (NI) ||start Expiry | | | |
| ||Timer | | | |
+------------++-------------+--------------+-------------+-------------+
| ||-> J state | -> PP state | - | -> NI state |
|Join (J) ||restart | start Prune- | | |
| ||Expiry Timer | Pending | | |
| || | Timer | | |
+------------++-------------+--------------+-------------+-------------+
|Prune- ||-> J state | -> PP state | -> NI state | -> NI state |
|Pending (PP)||restart | | Send Prune- | |
| ||Expiry Timer | | Echo(*,G) | |
+------------++-------------+--------------+-------------+-------------+
Now, the transition events "Receive Join(,G)" and "Receive Prune(,G)" mean a Join or Prune that's targeted at this router's primary IP address on the interface it came in on ๐ฏ. If the upstream neighbor address field isn't correct, these state transitions MUST NOT occur ๐ซ, although seeing a packet like that can still cause transitions in other state machines ๐คทโโ๏ธ.
On unnumbered interfaces on point-to-point links, the router's address should be the same as the source address it picked for the Hello message it sent over that interface. ๐ก But on point-to-point links it is RECOMMENDED that, for backwards compatibility, PIM Join/Prune messages with an upstream neighbor address field of all zeros also get accepted ๐ซถ.
Transitions from NoInfo State
When in NoInfo state, the following event may trigger a transition:
Receive Join(*,G): a Join(,G) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I. โ The (,G) downstream state machine on interface I moves to the Join state. The Expiry Timer (ET) starts and gets set to the HoldTime from the triggering Join/Prune message โณ.
Transitions from Join State
When in Join state, the following events may trigger a transition:
Receive Join(*,G): a Join(,G) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I. ๐ The (,G) downstream state machine on interface I stays in Join state, and the Expiry Timer (ET) gets restarted. The ET is set to the maximum of its current value and the HoldTime from the triggering Join/Prune message ๐.Receive Prune(*,G): a Prune(,G) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I. ๐ฌ The (,G) downstream state machine on interface I moves to the Prune-Pending state. The Prune-Pending Timer starts. It's set to the J/P_Override_Interval(I) if the router has more than one neighbor on that interface. Otherwise it's set to zero, so it expires immediately โก.- Expiry Timer Expires: the Expiry Timer for the (,G) downstream state machine on interface I expires. ๐ The (,G) downstream state machine on interface I moves to the NoInfo state.
Transitions from Prune-Pending State
When in Prune-Pending state, the following events may trigger a transition:
Receive Join(*,G): a Join(,G) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I. ๐ The (,G) downstream state machine on interface I moves to the Join state. The Prune-Pending Timer gets canceled (without triggering an expiry event). The Expiry Timer (ET) is restarted and then set to the maximum of its current value and the HoldTime from the triggering Join/Prune message ๐.- Expiry Timer Expires: the Expiry Timer for the (,G) downstream state machine on interface I expires. ๐ค The (,G) downstream state machine on interface I moves to the NoInfo state.
- Prune-Pending Timer Expires: the Prune-Pending Timer for the (,G) downstream state machine on interface I expires. ๐ท The (,G) downstream state machine on interface I moves to the NoInfo state, and a PruneEcho(*,G) gets sent onto the subnet connected to interface I ๐ข.
The action "Send PruneEcho(,G)" happens when the router stops forwarding on an interface because of a prune ๐. A PruneEcho(,G) is basically a Prune(,G) message sent by the upstream router on a LAN, with its *own address in the Upstream Neighbor Address field ๐ช. Its whole purpose is extra reliability. If a Prune that should've been overridden by another router gets lost locally on the LAN, the PruneEcho might still be received and make the override happen ๐ฆ. A PruneEcho(*,G) doesn't need to be sent on an interface that had only a single PIM neighbor while this state machine was in Prune-Pending state ๐ซง.
4.5.2. Receiving (S,G) Join/Prune Messages ๐
The per-interface state machine for receiving (S,G) Join/Prune messages is almost identical to the (*,G) one, so if you get that, you basically get this too ๐. It also has three states:
- NoInfo (NI): the interface has no (S,G) Join state and no (S,G) timers running. ๐ซฅ
- Join (J): the interface has (S,G) Join state, which means the router forwards packets from S destined for G out of this interface, but only if the (S,G) state is active (the SPTbit is set), except if the router lost an assert on this interface ๐.
- Prune-Pending (PP): the router got a Prune(S,G) on this interface from a downstream neighbor and is waiting to see whether another downstream router overrides it ๐ค. For forwarding purposes, Prune-Pending works exactly like Join, periodt ๐ .
There are two timers here too โฑ๏ธ:
- Expiry Timer (ET): set when a valid Join(S,G) is received. When it expires, this state machine goes back to NoInfo state ๐ฅ.
- Prune-Pending Timer (PPT): set when a valid Prune(S,G) is received. When it expires, this state machine goes back to NoInfo state ๐.
Figure 3: Downstream Per-Interface (S,G) State Machine
+------------++--------------------------------------------------------+
| || Event |
| ++-------------+--------------+-------------+-------------+
|Prev State ||Receive | Receive | Prune- | Expiry Timer|
| ||Join(S,G) | Prune(S,G) | Pending | Expires |
| || | | Timer | |
| || | | Expires | |
+------------++-------------+--------------+-------------+-------------+
| ||-> J state | -> NI state | - | - |
|NoInfo (NI) ||start Expiry | | | |
| ||Timer | | | |
+------------++-------------+--------------+-------------+-------------+
| ||-> J state | -> PP state | - | -> NI state |
|Join (J) ||restart | start Prune- | | |
| ||Expiry Timer | Pending | | |
| || | Timer | | |
+------------++-------------+--------------+-------------+-------------+
|Prune- ||-> J state | -> PP state | -> NI state | -> NI state |
|Pending (PP)||restart | | Send Prune- | |
| ||Expiry Timer | | Echo(S,G) | |
Okay so the transition events "Receive Join(S,G)" and "Receive Prune(S,G)" basically mean a Join or Prune aimed at this router's primary IP address on the interface it came in on ๐ If the upstream neighbor address field is wrong, these state transitions in this state machine MUST NOT occur ๐ โโ๏ธ although seeing that packet might still poke other state machines ๐
On unnumbered interfaces on point-to-point links, the router's address SHOULD be the same as the source address it picked for its Hello message on that interface ๐ซถ BUT (plot twist) on point-to-point links it is RECOMMENDED, for backwards compatibility, that PIM Join/Prune messages with an upstream neighbor address field of all zeros also get accepted ๐ง
Transitions from NoInfo State ๐ธ
When in NoInfo state, the following event may trigger a transition:
Receive Join(S,G) โจ A Join(S,G) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I.
The (S,G) downstream state machine on interface I transitions to the Join state. The Expiry Timer (ET) is started and set to the HoldTime from the triggering Join/Prune message. Main character energy ๐
Transitions from Join State ๐ฆ
When in Join state, the following events may trigger a transition:
Receive Join(S,G) ๐ A Join(S,G) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I.
The (S,G) downstream state machine on interface I remains in Join state. The Expiry Timer (ET) is restarted and then set to the maximum of its current value and the HoldTime from the triggering Join/Prune message. Bestie, it only ever goes up ๐
Receive Prune(S,G) ๐ A Prune(S,G) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I.
The (S,G) downstream state machine on interface I transitions to the Prune-Pending state. The Prune-Pending Timer is started. It gets set to the J/P_Override_Interval(I) if the router has more than one neighbor on that interface. Otherwise it gets set to zero, so it expires immediately โฐ
Expiry Timer Expires ๐ The Expiry Timer for the (S,G) downstream state machine on interface I expires.
The (S,G) downstream state machine on interface I transitions to the NoInfo state. Gone, reduced, nothing left ๐ซ
Transitions from Prune-Pending State ๐
When in Prune-Pending state, the following events may trigger a transition:
Receive Join(S,G) ๐ A Join(S,G) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I.
The (S,G) downstream state machine on interface I transitions to the Join state. The Prune-Pending Timer is canceled (without triggering an expiry event). The Expiry Timer (ET) is restarted and then set to the maximum of its current value and the HoldTime from the triggering Join/Prune message. A comeback, iconic ๐
Expiry Timer Expires ๐ The Expiry Timer for the (S,G) downstream state machine on interface I expires.
The (S,G) downstream state machine on interface I transitions to the NoInfo state.
Prune-Pending Timer Expires โญ The Prune-Pending Timer for the (S,G) downstream state machine on interface I expires.
The (S,G) downstream state machine on interface I transitions to the NoInfo state. A PruneEcho(S,G) is sent onto the subnet connected to interface I.
The action "Send PruneEcho(S,G)" is triggered when the router stops forwarding on an interface as a result of a prune. A PruneEcho(S,G) is simply a Prune(S,G) message sent by the upstream router on a LAN with its own address in the Upstream Neighbor Address field. Its purpose is extra reliability ๐ so that if a Prune that should have been overridden by another router gets lost locally on the LAN, the PruneEcho may be received and cause the override to happen. A PruneEcho(S,G) need not be sent on an interface that contains only a single PIM neighbor during the time this state machine was in Prune-Pending state. Double-checking the backup plan, very responsible of us ๐งโโ๏ธ
4.5.3. Receiving (S,G,rpt) Join/Prune Messages ๐ฆ
The per-interface state machine for receiving (S,G,rpt) Join/Prune messages is given below. There are five states:
- NoInfo (NI): The interface has no (S,G,rpt) Prune state and no (S,G,rpt) timers running. Quiet vibes ๐
- Prune (P): The interface has (S,G,rpt) Prune state, which will cause the router not to forward packets from S destined for G from this interface, even though the interface has active (*,G) Join state. Blocked, but make it cute ๐ซ๐
- Prune-Pending (PP): The router has received a Prune(S,G,rpt) on this interface from a downstream neighbor and is waiting to see whether the prune will be overridden by another downstream router. For forwarding purposes, the Prune-Pending state functions exactly like the NoInfo state. Wait and see ๐
- PruneTmp (P'): This state is a transient state that, for forwarding purposes, behaves exactly like the Prune state. A (,G) Join has been received (which may cancel the (S,G,rpt) Prune). As we parse the Join/Prune message from top to bottom, we first enter this state if the message contains a (,G) Join. Later in the message we will normally encounter an (S,G,rpt) prune to reinstate the Prune state. However, if we reach the end of the message without encountering such an (S,G,rpt) prune, then we revert to NoInfo state in this state machine. No time is spent here, so no timers can expire โก
- Prune-Pending-Tmp (PP'): This state is a transient state that is identical to P' except that it is associated with the PP state rather than the P state. For forwarding purposes, PP' behaves exactly like the PP state. Same energy, different outfit ๐
In addition, there are two timers:
- Expiry Timer (ET): This timer is set when a valid Prune(S,G,rpt) is received. Expiry of the Expiry Timer causes this state machine to revert to NoInfo state. โณ
- Prune-Pending Timer (PPT): This timer is set when a valid Prune(S,G,rpt) is received. Expiry of the Prune-Pending Timer causes this state machine to move on to Prune state. ๐
Figure 4: Downstream Per-Interface (S,G,rpt) State Machine
+----------++----------------------------------------------------------+
| || Event |
| ++---------+----------+----------+--------+--------+--------+
|Prev ||Receive | Receive | Receive | End of | Prune- | Expiry |
|State ||Join(*,G)| Join | Prune | Message| Pending| Timer |
| || | (S,G,rpt)| (S,G,rpt)| | Timer | Expires|
| || | | | | Expires| |
+----------++---------+----------+----------+--------+--------+--------+
| ||- | - | -> PP | - | - | - |
| || | | state | | | |
| || | | start | | | |
|NoInfo || | | Prune- | | | |
|(NI) || | | Pending | | | |
| || | | Timer; | | | |
| || | | start | | | |
| || | | Expiry | | | |
| || | | Timer | | | |
+----------++---------+----------+----------+--------+--------+--------+
| ||-> P' | -> NI | -> P | - | - | -> NI |
| ||state | state | state | | | state |
| || | | restart | | | |
| || | | Expiry | | | |
| || | | Timer | | | |
+----------++---------+----------+----------+--------+--------+--------+
|Prune- ||-> PP' | -> NI | - | - | -> P | - |
|Pending ||state | state | | | state | |
|(PP) || | | | | | |
+----------++---------+----------+----------+--------+--------+--------+
| ||- | - | -> P | -> NI | - | - |
|PruneTmp || | | state | state | | |
|(P') || | | restart | | | |
| || | | Expiry | | | |
| || | | Timer | | | |
+----------++---------+----------+----------+--------+--------+--------+
| ||- | - | -> PP | -> NI | - | - |
|Prune- || | | state | state | | |
|Pending- || | | restart | | | |
|Tmp (PP') || | | Expiry | | | |
| || | | Timer | | | |
+----------++---------+----------+----------+--------+--------+--------+
okay so the transition events Receive Join(S,G,rpt), Receive Prune(S,G,rpt), and Receive Join(*,G) all basically mean a Join or Prune just landed on this router's primary IP address, on the interface it came in on ๐ฌ. If the upstream neighbor address field isn't correct, these state transitions in this state machine MUST NOT occur ๐
โโ๏ธ, although seeing that packet may still cause state transitions in other state machines. ๐
On unnumbered interfaces on point-to-point links, the router's address should be the same as the source address it picked for its Hello message on that interface. ๐ BUT on point-to-point links it is RECOMMENDED that Join/Prune messages with an upstream neighbor address field of all zeros also get accepted ๐ซถ this is the kind of detail that lowkey matters a lot ngl.
Transitions from NoInfo State โจ
When in NoInfo (NI) state, the following event may trigger a transition:
Receive Prune(S,G,rpt): a Prune(S,G,rpt) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I. The (S,G,rpt) downstream state machine on interface I transitions to the Prune-Pending state. The Expiry Timer (ET) is started and set to the HoldTime from the triggering Join/Prune message. The Prune-Pending Timer is started. It is set to the J/P_Override_Interval(I) if the router has more than one neighbor on that interface; otherwise, it is set to zero, causing it to expire immediately. โฐ
Transitions from Prune-Pending State ๐ธ
When in Prune-Pending (PP) state, the following events may trigger a transition:
Receive Join(*,G): a Join(*,G) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I. The (S,G,rpt) downstream state machine on interface I transitions to the Prune-Pending-Tmp state whilst the remainder of the compound Join/Prune message containing the Join(*,G) is processed.
Receive Join(S,G,rpt): a Join(S,G,rpt) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I. The (S,G,rpt) downstream state machine on interface I transitions to the NoInfo state. The ET and PPT are canceled. ๐
Prune-Pending Timer Expires: the Prune-Pending Timer for the (S,G,rpt) downstream state machine on interface I expires. The (S,G,rpt) downstream state machine on interface I transitions to the Prune state.
Transitions from Prune State ๐ง
When in Prune (P) state, the following events may trigger a transition:
Receive Join(*,G): a Join(*,G) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I. The (S,G,rpt) downstream state machine on interface I transitions to the PruneTmp state whilst the remainder of the compound Join/Prune message containing the Join(*,G) is processed.
Receive Join(S,G,rpt): a Join(S,G,rpt) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I. The (S,G,rpt) downstream state machine on interface I transitions to the NoInfo state. The ET and PPT are canceled.
Receive Prune(S,G,rpt): a Prune(S,G,rpt) is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I. The (S,G,rpt) downstream state machine on interface I remains in Prune state. The Expiry Timer (ET) is restarted and is then set to the maximum of its current value and the HoldTime from the triggering Join/Prune message. ๐
Expiry Timer Expires: the Expiry Timer for the (S,G,rpt) downstream state machine on interface I expires. The (S,G,rpt) downstream state machine on interface I transitions to the NoInfo state.
Transitions from Prune-Pending-Tmp State ๐ฆ
When in Prune-Pending-Tmp (PP') state and processing a compound Join/Prune message, the following events may trigger a transition:
Receive Prune(S,G,rpt): the compound Join/Prune message contains a Prune(S,G,rpt) that is received on interface I with its Upstream Neighbor Address set to the router's primary IP address on I. The (S,G,rpt) downstream state machine on interface I transitions back to the Prune-Pending state. The Expiry Timer (ET) is restarted and is then set to the maximum of its current value and the HoldTime from the triggering Join/Prune message.
End of Message: the end of the compound Join/Prune message is reached. The (S,G,rpt) downstream state machine on interface I transitions to the NoInfo state. The ET and PPT are canceled. ๐
Transitions from PruneTmp State ๐
When in PruneTmp (P') state and processing a compound Join/Prune message, the following events may trigger a transition:
Receive Prune(S,G,rpt): the compound Join/Prune message contains a Prune(S,G,rpt). The (S,G,rpt) downstream state machine on interface I transitions back to the Prune state. The Expiry Timer (ET) is restarted and is then set to the maximum of its current value and the HoldTime from the triggering Join/Prune message.
End of Message: the end of the compound Join/Prune message is reached. The (S,G,rpt) downstream state machine on interface I transitions to the NoInfo state. ET is canceled.
Note: Receiving a Prune(*,G) does not affect the (S,G,rpt) downstream state machine. ๐
4.5.4. Sending (*,G) Join/Prune Messages โจ
okay bestie, the per-interface state machines for (*,G) hold join state from downstream PIM routers ๐ and that state is what decides whether a router needs to propagate a Join(*,G) upstream towards the RP.
If a router wishes to propagate a Join(*,G) upstream, it must also watch for messages on its upstream interface from other routers on that subnet, and these may modify its behavior. ๐ If it sees a Join(*,G) to the correct upstream neighbor, it should suppress its own Join(*,G). If it sees a Prune(*,G) to the correct upstream neighbor, it should be prepared to override that prune by sending a Join(*,G) almost immediately. Finally, if it sees the Generation ID (see Section 4.3) of the correct upstream neighbor change, it knows that the upstream neighbor has lost state, and it should be prepared to refresh the state by sending a Join(*,G) almost immediately. ๐
If a (*,G) Assert occurs on the upstream interface, and this changes this router's idea of the upstream neighbor, it should be prepared to ensure that the Assert winner is aware of downstream routers by sending a Join(*,G) almost immediately. ๐ซถ
In addition, if the MRIB changes to indicate that the next hop towards the RP has changed, and either the upstream interface changes or there is no Assert winner on the upstream interface, the router should prune off from the old next hop and join towards the new next hop. ๐ฅ
The upstream (*,G) state machine only contains two states:
- Not Joined: the downstream state machines indicate that the router does not need to join the RP tree for this group.
- Joined: the downstream state machines indicate that the router should join the RP tree for this group.
In addition, one timer JT(,G) is kept that is used to trigger the sending of a Join(*,G) to the upstream next hop towards the RP, RPF'(,G). โฑ๏ธ
Here's Figure 5: Upstream (*,G) State Machine ๐
Figure 5: Upstream (*,G) State Machine
+-------------------++-------------------------------------------------+
| || Event |
| Prev State ++------------------------+------------------------+
| || JoinDesired(*,G) | JoinDesired(*,G) |
| || ->True | ->False |
+-------------------++------------------------+------------------------+
| || -> J state | - |
| NotJoined (NJ) || Send Join(*,G); | |
| || set Join Timer to | |
| || t_periodic | |
+-------------------++------------------------+------------------------+
| Joined (J) || - | -> NJ state |
| || | Send Prune(*,G); |
| || | cancel Join Timer |
+-------------------++------------------------+------------------------+
In addition, we have the following transitions, which occur within the Joined state: ๐
+----------------------------------------------------------------------+
| In Joined (J) State |
+----------------+-----------------+-----------------+-----------------+
|Timer Expires | See Join(*,G) | See Prune(*,G) | RPF'(*,G) |
| | to RPF'(*,G) | to RPF'(*,G) | changes due to |
| | | | an Assert |
+----------------+-----------------+-----------------+-----------------+
|Send | Increase Join | Decrease Join | Decrease Join |
|Join(*,G); set | Timer to | Timer to | Timer to |
|Join Timer to | t_joinsuppress | t_override | t_override |
|t_periodic | | | |
+----------------+-----------------+-----------------+-----------------+
+----------------------------------------------------------------------+
| In Joined (J) State |
+----------------------------------+-----------------------------------+
| RPF'(*,G) changes not | RPF'(*,G) GenID changes |
| due to an Assert | |
+----------------------------------+-----------------------------------+
| Send Join(*,G) to new | Decrease Join Timer to |
| next hop; send | t_override |
| Prune(*,G) to old next | |
| hop; set Join Timer to | |
| t_periodic | |
+----------------------------------+-----------------------------------+
This state machine uses the following macro:
bool JoinDesired(*,G) {
if (immediate_olist(*,G) != NULL)
return TRUE
else
return FALSE
}
okay so JoinDesired(*,G) is basically TRUE when the router has forwarding state that would make it forward G traffic over the shared tree ๐ณ. ngl the wild part is that even when it's TRUE, the router might not actually send its Join(*,G), because another router already sent one onto the upstream interface ๐ซถ.
Transitions from NotJoined State ๐ฆ
When the upstream (*,G) state machine is in NotJoined state, the following event may trigger a state transition:
JoinDesired(,G) becomes True The macro JoinDesired(*,G) flips to True, e.g., because the downstream state for (,G) changed so that at least one interface is in immediate_olist(*,G) โจ. The upstream (*,G) state machine moves to the Joined state. It sends Join(*,G) to the right upstream neighbor, which is RPF'(*,G), and sets the Join Timer (JT) to expire after t_periodic seconds ๐.
Transitions from Joined State ๐ธ
When the upstream (*,G) state machine is in Joined state, the following events may trigger state transitions:
JoinDesired(,G) becomes False The macro JoinDesired(*,G) goes False, e.g., because the downstream state for (,G) changed so that no interface is in immediate_olist(*,G) ๐. The upstream (*,G) state machine goes back to NotJoined. It sends Prune(*,G) to the right upstream neighbor, RPF'(*,G), and cancels the Join Timer (JT).
Join Timer Expires The Join Timer (JT) went off, which means it's time to send a Join(*,G) โฐ. It sends Join(*,G) to RPF'(*,G) and restarts the Join Timer (JT) to expire after t_periodic seconds.
See Join(,G) to RPF'(,G) This one only matters if RPF_interface(RP(G)) is a shared medium ๐ . The router sees another router on RPF_interface(RP(G)) send a Join(*,G) to RPF'(*,G), which makes this router suppress its own Join ๐ค. The upstream (*,G) state machine stays in Joined state. Let t_joinsuppress be the minimum of t_suppressed and the HoldTime from the Join/Prune message that triggered this event. If the Join Timer is set to expire in less than t_joinsuppress seconds, reset it so it expires after t_joinsuppress seconds. If the Join Timer is set to expire in more than t_joinsuppress seconds, leave it alone โจ.
See Prune(,G) to RPF'(,G) Also only relevant if RPF_interface(RP(G)) is a shared medium ๐. The router sees another router on that interface send a Prune(*,G) to RPF'(*,G). Since this router is Joined, it has to override the Prune after a short random interval ๐
. The upstream (*,G) state machine stays in Joined state. If the Join Timer is set to expire in more than t_override seconds, reset it to expire after t_override seconds. If it's set to expire in less than t_override seconds, leave it unchanged.
RPF'(,G) changes due to an Assert The current next hop towards the RP changes because of an Assert(*,G) on RPF_interface(RP(G)) ๐. The upstream (,G) state machine stays in Joined state. If the Join Timer is set to expire in more than t_override seconds, reset it to expire after t_override seconds. If it's set to expire in less than t_override seconds, leave it unchanged.
RPF'(,G) changes not due to an Assert Something else happened that changed the next hop towards the RP for G ๐. This might be a change in the MRIB routing database or in the group-to-RP mapping. Note that this transition does NOT happen if an Assert is active and the upstream interface doesn't change. The upstream (,G) state machine stays in Joined state. It sends Join(*,G) to the new upstream neighbor, which is the new value of RPF'(*,G), and sends Prune(*,G) to the old upstream neighbor, which is the old value of RPF'(*,G). It uses the new value of RP(G) in the Prune(*,G) message, or all zeros if RP(G) becomes unknown. (The old value of RP(G) may be used instead to improve behavior in routers implementing older versions of this specification ๐ง.) It sets the Join Timer (JT) to expire after t_periodic seconds.
RPF'(,G) GenID changes The Generation ID of the router that is RPF'(*,G) changed ๐. This normally means that neighbor lost state, so the state has to be refreshed ๐. The upstream (,G) state machine stays in Joined state. If the Join Timer is set to expire in more than t_override seconds, reset it to expire after t_override seconds.
4.5.5. Sending (S,G) Join/Prune Messages โจ
Okay so the per-interface (S,G) state machines are where the router keeps join state from its downstream PIM routers ๐
. That state decides whether the router has to pass a Join(S,G) upstream toward the source ๐ธ.
If a router wants to push a Join(S,G) upstream, it also has to keep an eye on its upstream interface ๐, because other routers on that subnet can change what it should do. If it sees a Join(S,G) going to the correct upstream neighbor, it should hold back its own Join(S,G) ๐ซถ. If it sees a Prune(S,G), Prune(S,G,rpt), or Prune(*,G) going to the correct upstream neighbor toward S, it should be ready to override that prune by scheduling a Join(S,G) to go out almost immediately โก. And if the Generation ID of its upstream neighbor changes, it knows that neighbor lost state, so it should refresh things by scheduling a Join(S,G) almost immediately ๐.
If an (S,G) Assert happens on the upstream interface and it changes this router's idea of the upstream neighbor, the router should be ready to make sure the Assert winner knows about its downstream routers ๐. It does that by scheduling a Join(S,G) to be sent almost immediately.
In addition, if MRIB changes make the next hop towards the source change, and either the upstream interface changes or there's no Assert winner on the upstream interface, the router should send a prune to the old next hop and a join to the new next hop ๐ฆ.
The upstream (S,G) state machine only has two states:
- Not Joined: The downstream state machines and local membership information don't indicate that the router needs to join the shortest-path tree for this (S,G) ๐.
- Joined: The downstream state machines and local membership information indicate that the router should join the shortest-path tree for this (S,G) ๐.
One timer, JT(S,G), is also kept. It's used to trigger sending a Join(S,G) to the upstream next hop towards S, RPF'(S,G) โฐ.
Figure 6: Upstream (S,G) State Machine ๐ง
+-------------------+--------------------------------------------------+
| | Event |
| Prev State +-------------------------+------------------------+
| | JoinDesired(S,G) | JoinDesired(S,G) |
| | ->True | ->False |
+-------------------+-------------------------+------------------------+
| NotJoined (NJ) | -> J state | - |
| | Send Join(S,G); | |
| | set Join Timer to | |
| | t_periodic | |
+-------------------+-------------------------+------------------------+
| Joined (J) | - | -> NJ state |
| | | Send Prune(S,G); |
| | | set SPTbit(S,G) to |
| | | FALSE; cancel Join |
| | | Timer |
+-------------------+-------------------------+------------------------+
In addition, we have the following transitions, which happen within the Joined state ๐:
+----------------------------------------------------------------------+
| In Joined (J) State |
+-----------------+-----------------+-----------------+----------------+
| Timer Expires | See Join(S,G) | See Prune(S,G) | See Prune |
| | to RPF'(S,G) | to RPF'(S,G) | (S,G,rpt) to |
| | | | RPF'(S,G) |
+-----------------+-----------------+-----------------+----------------+
| Send | Increase Join | Decrease Join | Decrease Join |
| Join(S,G); set | Timer to | Timer to | Timer to |
| Join Timer to | t_joinsuppress | t_override | t_override |
| t_periodic | | | |
+-----------------+-----------------+-----------------+----------------+
+----------------------------------------------------------------------+
| In Joined (J) State |
+-----------------+-----------------+----------------+-----------------+
| See Prune(*,G) | RPF'(S,G) | RPF'(S,G) | RPF'(S,G) |
| to RPF'(S,G) | changes not | GenID changes | changes due to |
| | due to an | | an Assert |
| | Assert | | |
+-----------------+-----------------+----------------+-----------------+
| Decrease Join | Send Join(S,G) | Decrease Join | Decrease Join |
| Timer to | to new next | Timer to | Timer to |
| t_override | hop; send | t_override | t_override |
| | Prune(S,G) to | | |
| | old next hop; | | |
| | set Join Timer | | |
| | to t_periodic | | |
+-----------------+-----------------+----------------+-----------------+
okay so this state machine leans on one little macro, and it's the boss of the whole thing ๐
bool JoinDesired(S,G) {
return( immediate_olist(S,G) != NULL
OR ( KeepaliveTimer(S,G) is running
AND inherited_olist(S,G) != NULL ) )
}
JoinDesired(S,G) is true when the router has forwarding state that would make it forward traffic for G using source tree state ๐ That source tree state can come from either active source-specific join state, or from the (S,G) Keepalive Timer plus active non-source-specific state. Not me getting excited about this bit ๐ซถ Also, even when JoinDesired is true, the router might not send its Join(S,G) at all, because another router already sent a Join(S,G) onto the upstream interface and suppressed it โจ
Transitions from NotJoined State
When the upstream (S,G) state machine is in NotJoined state, this event can kick off a transition:
JoinDesired(S,G) becomes True Basically, the macro JoinDesired(S,G) flips to True, maybe because the downstream state for (S,G) changed so that at least one interface is now in inherited_olist(S,G). ๐ฆ The upstream (S,G) state machine moves to the Joined state. It sends Join(S,G) to the right upstream neighbor, which is RPF'(S,G). It also sets the Join Timer (JT) to expire after t_periodic seconds. Iconic behaviour, honestly ๐
Transitions from Joined State
When the upstream (S,G) state machine is in Joined state, these events can trigger transitions. Let's go, bestie ๐
JoinDesired(S,G) becomes False Here the macro JoinDesired(S,G) goes False, maybe because the downstream state for (S,G) changed so no interface is in inherited_olist(S,G) anymore ๐ฅ The upstream (S,G) state machine goes back to NotJoined. It sends Prune(S,G) to the right upstream neighbor, which is RPF'(S,G). It cancels the Join Timer (JT) and sets SPTbit(S,G) to FALSE. Bye bye, bestie ๐
Join Timer Expires The Join Timer (JT) runs out, which means it's time to send a Join(S,G) ๐ฅ So it sends Join(S,G) to the right upstream neighbor, which is RPF'(S,G), and restarts the Join Timer (JT) to expire after t_periodic seconds. Periodic, but make it slay โจ
See Join(S,G) to RPF'(S,G) This one only matters if RPF_interface(S) is a shared medium. This router sees another router on RPF_interface(S) send a Join(S,G) to RPF'(S,G), and that makes this router hold back its own Join ๐ซถ The upstream (S,G) state machine stays in Joined state.
Let t_joinsuppress be the minimum of t_suppressed and the HoldTime from the Join/Prune message that triggered this event. If the Join Timer is set to expire in less than t_joinsuppress seconds, reset it so it expires after t_joinsuppress seconds. If the Join Timer is set to expire in more than t_joinsuppress seconds, leave it alone ๐ธ
See Prune(S,G) to RPF'(S,G) Also only relevant if RPF_interface(S) is a shared medium. This router sees another router on RPF_interface(S) send a Prune(S,G) to RPF'(S,G). Since this router is in Joined state, it has to override the Prune after a short random interval ๐ค The upstream (S,G) state machine stays in Joined state. If the Join Timer is set to expire in more than t_override seconds, reset it so it expires after t_override seconds. Main character override energy ๐
See Prune(S,G,rpt) to RPF'(S,G) Again, only relevant if RPF_interface(S) is a shared medium. This router sees another router on RPF_interface(S) send a Prune(S,G,rpt) to RPF'(S,G). If the upstream router is an RFC-2362-compliant PIM router, then that Prune(S,G,rpt) will make it stop forwarding. For backwards compatibility, this router should override the prune so forwarding keeps going ๐ง
The upstream (S,G) state machine stays in Joined state. If the Join Timer is set to expire in more than t_override seconds, reset it so it expires after t_override seconds.
See Prune(,G) to RPF'(S,G) Only relevant if RPF_interface(S) is a shared medium. This router sees another router on RPF_interface(S) send a Prune(,G) to RPF'(S,G). If the upstream router is an RFC-2362-compliant PIM router, then that Prune(*,G) will make it stop forwarding. For backwards compatibility, this router should override the prune so forwarding continues ๐
The upstream (S,G) state machine stays in Joined state. If the Join Timer is set to expire in more than t_override seconds, reset it so it expires after t_override seconds.
RPF'(S,G) changes due to an Assert The current next hop towards S changes because of an Assert(S,G) on the RPF_interface(S) ๐ฆ The upstream (S,G) state machine stays in Joined state. If the Join Timer is set to expire in more than t_override seconds, reset it so it expires after t_override seconds. If the Join Timer is set to expire in less than t_override seconds, leave it unchanged.
RPF'(S,G) changes not due to an Assert Something else happened that changed the next hop towards S. Quick note: this transition does not happen if an Assert is active and the upstream interface doesn't change ๐ The upstream (S,G) state machine stays in Joined state. It sends Join(S,G) to the new upstream neighbour, which is the new value of RPF'(S,G). It sends Prune(S,G) to the old upstream neighbour, which is the old value of RPF'(S,G). It sets the Join Timer (JT) to expire after t_periodic seconds. Packing up and moving houses, periodt ๐
RPF'(S,G) GenID changes The Generation ID of the router that is RPF'(S,G) changes. This usually means that neighbour has lost state, so the state needs a refresh ๐ฅบ The upstream (S,G) state machine stays in Joined state. If the Join Timer is set to expire in more than t_override seconds, reset it so it expires after t_override seconds.
4.5.6. (S,G,rpt) Periodic Messages โจ
(S,G,rpt) Joins and Prunes are (S,G) Joins or Prunes sent on the RP tree with the RPT bit set. They're there either to modify the results of (,G) Joins, or to override what other upstream LAN peers are doing ๐ The next section explains the rules for sending triggered messages. This section explains the rules for including a Prune(S,G,rpt) message with a Join(,G).
When a router is going to send a Join(*,G), it should use the following pseudocode for each (S,G) it has state for. That helps it decide whether to include a Prune(S,G,rpt) in the compound Join/Prune message ๐ง
if( SPTbit(S,G) == TRUE ) {
# Note: If receiving (S,G) on the SPT, we only prune off the
# shared tree if the RPF neighbors differ.
if( RPF'(*,G) != RPF'(S,G) ) {
add Prune(S,G,rpt) to compound message
}
} else if ( inherited_olist(S,G,rpt) == NULL ) {
# Note: All (*,G) olist interfaces received RPT prunes for (S,G).
add Prune(S,G,rpt) to compound message
} else if ( RPF'(*,G) != RPF'(S,G,rpt) {
# Note: We joined the shared tree, but there was an (S,G) assert
# and the source tree RPF neighbor is different.
add Prune(S,G,rpt) to compound message
}
Note that Join(S,G,rpt) is normally not sent as a periodic message. It's only sent as a triggered message ๐
4.5.7. State Machine for (S,G,rpt) Triggered Messages ๐
The state machine for (S,G,rpt) triggered messages is needed per-(S,G) whenever there's (*,G) join state at a router, and the router or any of its upstream LAN peers wants to prune S off the RP tree ๐ซถ
There are three states in this state machine. One of them is for when there's no (,G) join state at this router. If there is (,G) join state at the router, the state machine has to be in one of the other two states. The three states are:
- Pruned(S,G,rpt): (*,G) Joined, but (S,G,rpt) pruned.
- NotPruned(S,G,rpt): (*,G) Joined, and (S,G,rpt) not pruned.
- RPTNotJoined(G): (*,G) has not been joined.
There's also an (S,G,rpt) Override Timer, OT(S,G,rpt). It delays triggered Join(S,G,rpt) messages so they don't implode all at once ๐ฅ
Figure 7: Upstream (S,G,rpt) State Machine for Triggered Messages
+------------++--------------------------------------------------------+
| || Event |
| ++--------------+--------------+-------------+------------+
|Prev State || PruneDesired | PruneDesired | RPTJoin | inherited_ |
| || (S,G,rpt) | (S,G,rpt) | Desired(G) | olist |
| || ->True | ->False | ->False | (S,G,rpt) |
| || | | | ->non-NULL |
+------------++--------------+--------------+-------------+------------+
|RPTNotJoined|| -> P state | - | - | -> NP state|
|(G) (NJ) || | | | |
+------------++--------------+--------------+-------------+------------+
|Pruned || - | -> NP state | -> NJ state | - |
|(S,G,rpt) || | Send Join | | |
|(P) || | (S,G,rpt) | | |
+------------++--------------+--------------+-------------+------------+
|NotPruned || -> P state | - | -> NJ state | - |
|(S,G,rpt) || Send Prune | | Cancel OT | |
|(NP) || (S,G,rpt); | | | |
| || cancel OT | | | |
+------------++--------------+--------------+-------------+------------+
Additionally, there are some more transitions inside the NotPruned(S,G,rpt) state. They're all used for prune override behaviour, so they're a big deal ๐ฅ
+----------------------------------------------------------------------+
| In NotPruned(S,G,rpt) State |
+----------+--------------+--------------+--------------+--------------+
|Override | See Prune | See Join | See Prune | RPF' |
|Timer | (S,G,rpt) to | (S,G,rpt) to | (S,G) to | (S,G,rpt) -> |
|expires | RPF' | RPF' | RPF' | RPF' (*,G) |
| | (S,G,rpt) | (S,G,rpt) | (S,G,rpt) | |
+----------+--------------+--------------+--------------+--------------+
|Send Join | OT = min(OT, | Cancel OT | OT = min(OT, | OT = min(OT, |
|(S,G,rpt);| t_override) | | t_override) | t_override) |
|leave OT | | | | |
|unset | | | | |
Okay bestie, in that state machine the min function treats a timer that isn't running as infinity โพ๏ธ, so min(not-running, t_override) = t_override ๐
โจ
The macros are next ๐ง
bool RPTJoinDesired(G) {
return (JoinDesired(*,G))
}
RPTJoinDesired(G) is true when the router has forwarding state that would make it forward traffic for G using (*,G) shared tree state ๐ธ
bool PruneDesired(S,G,rpt) {
return ( RPTJoinDesired(G) AND
( inherited_olist(S,G,rpt) == NULL
OR (SPTbit(S,G)==TRUE
AND (RPF'(*,G) != RPF'(S,G)) )))
}
PruneDesired(S,G,rpt) can only be true if RPTJoinDesired(G) is true ๐ So if RPTJoinDesired(G) is true, PruneDesired(S,G,rpt) is true either when there are no outgoing interfaces that S would be forwarded on, or when the router has active (S,G) forwarding state but RPF'(*,G) != RPF'(S,G) ๐
Now the transition events, which are the main drama ๐ฅ
See Join(S,G,rpt) to RPF'(S,G,rpt) This one only matters in the "Not Pruned" state. The router sees a Join(S,G,rpt) from someone else going to RPF'(S,G,rpt), which is the correct upstream neighbor. If we're in "NotPruned" state and the (S,G,rpt) Override Timer is running, that means we got triggered to send our own Join(S,G,rpt) to RPF'(S,G,rpt), but someone else beat us to it ๐ญ So there's no need for us to send our own Join. The action is to cancel the Override Timer โจ
See Prune(S,G,rpt) to RPF'(S,G,rpt) This one only matters in the "NotPruned" state. The router sees a Prune(S,G,rpt) from someone else going to RPF'(S,G,rpt), which is the correct upstream neighbor. If we're in the "NotPruned" state, we still want to keep receiving traffic from S destined for G, and that traffic is being supplied by RPF'(S,G,rpt). So we need to override the Prune ๐ The action is to set the (S,G,rpt) Override Timer to the randomized prune-override interval, t_override. However, if the Override Timer is already running, we only set the timer if doing so would set it to a lower value. At the end of this interval, if no one else has sent a Join, then we will do so ๐ฆ
See Prune(S,G) to RPF'(S,G,rpt) This one only matters in the "NotPruned" state. This transition and action are the same as the one above, except the Prune does not have the RPT bit set. This transition is necessary to be compatible with routers implemented from RFC 2362 that don't maintain separate (S,G) and (S,G,rpt) state ๐ง
The (S,G,rpt) prune Override Timer expires This one only matters in the "NotPruned" state. When the Override Timer expires, we must send a Join(S,G,rpt) to RPF'(S,G,rpt) to override the Prune message that caused the timer to be running ๐ซถ We only send this if RPF'(S,G,rpt) equals RPF'(,G). If that isn't the case, the Join might get sent to a router that doesn't have (,G) Join state, and then the behavior would not be well defined ๐ If RPF'(S,G,rpt) is not the same as RPF'(,G), then it may stop forwarding S. However, if this happens, the router will send an AssertCancel(S,G), which would then cause RPF'(S,G,rpt) to become equal to RPF'(,G) (see below) ๐
RPF'(S,G,rpt) changes to become equal to RPF'(,G) This one only matters in the "NotPruned" state. RPF'(S,G,rpt) can only be different from RPF'(,G) if an (S,G) Assert has happened, which means traffic from S is arriving on the SPT. That means Prune(S,G,rpt) will have been sent to RPF'(,G) โญ When RPF'(S,G,rpt) changes to become equal to RPF'(,G), we need to trigger a Join(S,G,rpt) to RPF'(*,G) so that router starts forwarding S again. The action is to set the (S,G,rpt) Override Timer to the randomized prune-override interval t_override. However, if the timer is already running, we only set the timer if doing so would set it to a lower value. At the end of this interval, if no one else has sent a Join, then we will do so ๐
PruneDesired(S,G,rpt)->TRUE See the macro above. This event is relevant in the "NotPruned" and "RPTNotJoined(G)" states. The router wants to receive traffic for G but does not want to receive traffic from S destined for G. This makes the router transition into the Pruned state ๐
If the router was previously in the NotPruned state, the action is to send a Prune(S,G,rpt) to RPF'(S,G,rpt) and cancel the Override Timer. If the router was previously in the RPTNotJoined(G) state, there's no need to trigger an action in this state machine, because sending a Prune(S,G,rpt) is handled by the rules for sending the Join(*,G) ๐
PruneDesired(S,G,rpt)->FALSE See the macro above. This transition is only relevant in the "Pruned" state. If the router is in the Pruned(S,G,rpt) state and PruneDesired(S,G,rpt) changes to FALSE, that could be because the router no longer has RPTJoinDesired(G) true, or because it now wants to receive traffic from S again. If it's the former, then this transition should not happen, and the "RPTJoinDesired(G)->FALSE" transition should happen instead ๐ So this transition should be read as "PruneDesired(S,G,rpt)->FALSE AND RPTJoinDesired(G)==TRUE" โจ
The action is to send a Join(S,G,rpt) to RPF'(S,G,rpt) ๐ฆ
RPTJoinDesired(G)->FALSE This event is relevant in the "Pruned" and "NotPruned" states. The router no longer wants to receive any traffic destined for G on the RP Tree ๐ญ This causes a transition to the RPTNotJoined(G) state, and the Override Timer gets canceled if it was running. Any further actions are handled by the appropriate upstream state machine for (*,G) ๐ธ
inherited_olist(S,G,rpt) becomes non-NULL This transition is only relevant in the RPTNotJoined(G) state. The router has joined the RP tree (handled by the (*,G) upstream state machine as appropriate) and wants to receive traffic from S. This doesn't trigger any events in this state machine, but it does cause a transition to the NotPruned(S,G,rpt) state ๐
4.6. PIM Assert Messages ๐ฆ
Where multiple PIM routers peer over a shared LAN, it's possible for more than one upstream router to have valid forwarding state for a packet, which can lead to packet duplication (see Section 3.6) ๐ PIM doesn't try to stop this from happening. Instead, it detects when it has happened and elects a single forwarder among the upstream routers to prevent further duplication ๐ This election is done using PIM Assert messages. Downstream routers on the LAN also receive Assert messages, and these cause subsequent Join/Prune messages to be sent to the upstream router that won the Assert ๐ง
In general, a PIM Assert message should only be accepted for processing if it comes from a known PIM neighbor ๐ซถ A PIM router learns about PIM neighbors through PIM Hello messages. If a router receives an Assert message from a particular IP source address and it hasn't seen a PIM Hello message from that source address, then the Assert message SHOULD be discarded without further processing ๐ซ Also, if the Hello message from a neighbor was authenticated (see Section 6.3), then all Assert messages from that neighbor MUST also be authenticated ๐
Some older PIM implementations incorrectly fail to send Hello messages on point-to-point interfaces, so we also RECOMMEND that a configuration option be provided to allow interoperation with those older routers. But this configuration option SHOULD NOT be enabled by default ๐
4.6.1. (S,G) Assert Message State Machine โญ
The (S,G) Assert state machine for interface I is shown in Figure 8. There are three states ๐
NoInfo (NI) This router has no (S,G) assert state on interface I ๐
I am Assert Winner (W) This router has won an (S,G) assert on interface I. It is now responsible for forwarding traffic from S destined for G out of interface I. Whether or not it is the DR for I, while a router is the assert winner, it is also responsible for forwarding traffic onto I on behalf of local hosts on I that have made membership requests that specifically refer to S (and G) ๐
I am Assert Loser (L) This router has lost an (S,G) assert on interface I. It must not forward packets from S destined for G onto interface I ๐ If it is the DR on I, it is no longer responsible for forwarding traffic onto I to satisfy local hosts with membership requests that specifically refer to S and G โจ
There's also an Assert Timer (AT) that is used to time out asserts on the assert losers and to resend asserts on the assert winner โฐ
Figure 8: Per-Interface (S,G) Assert State Machine
+----------------------------------------------------------------------+
| In NoInfo (NI) State |
+---------------+-------------------+------------------+---------------+
| Receive | Receive Assert | Data arrives | Receive |
| Inferior | with RPTbit | from S to G on | Acceptable |
| Assert with | set and | I and | Assert with |
| RPTbit clear | CouldAssert | CouldAssert | RPTbit clear |
| | (S,G,I) | (S,G,I) | and AssTrDes |
| | | | (S,G,I) |
+---------------+-------------------+------------------+---------------+
| -> W state | -> W state | -> W state | -> L state |
| [Actions A1] | [Actions A1] | [Actions A1] | [Actions A6] |
+---------------+-------------------+------------------+---------------+
+----------------------------------------------------------------------+
| In I Am Assert Winner (W) State |
+----------------+------------------+-----------------+----------------+
| Assert Timer | Receive | Receive | CouldAssert |
| Expires | Inferior | Preferred | (S,G,I) -> |
| | Assert | Assert | FALSE |
+----------------+------------------+-----------------+----------------+
| -> W state | -> W state | -> L state | -> NI state |
| [Actions A3] | [Actions A3] | [Actions A2] | [Actions A4] |
+----------------+------------------+-----------------+----------------+
+---------------------------------------------------------------------+
| In I Am Assert Loser (L) State |
+-------------+-------------+-------------+-------------+-------------+
|Receive |Receive |Receive |Assert Timer |Current |
|Preferred |Acceptable |Inferior |Expires |Winner's |
|Assert |Assert with |Assert or | |GenID |
| |RPTbit clear |Assert | |Changes or |
| |from Current |Cancel from | |NLT Expires |
| |Winner |Current | | |
| | |Winner | | |
+-------------+-------------+-------------+-------------+-------------+
|-> L state |-> L state |-> NI state |-> NI state |-> NI state |
|[Actions A2] |[Actions A2] |[Actions A5] |[Actions A5] |[Actions A5] |
+-------------+-------------+-------------+-------------+-------------+
Okay so this table is basically the cheat sheet for the Assert Loser side of things. Each column is an event, and the bottom row tells you where you land and which Actions to run. Preferred asserts and acceptable asserts from the current winner send you to the L state with Actions A2. Inferior asserts, asserts cancels from the current winner, the Assert Timer expiring, and the current winner's GenID changing or NLT expiring all send you to NI with Actions A5. ๐
+----------------------------------------------------------------------+
| In I Am Assert Loser (L) State |
+----------------+-----------------+------------------+----------------+
| AssTrDes | my_metric -> | RPF_interface | Receive |
| (S,G,I) -> | better than | (S) stops | Join(S,G) on |
| FALSE | winner's | being I | interface I |
| | metric | | |
+----------------+-----------------+------------------+----------------+
| -> NI state | -> NI state | -> NI state | -> NI State |
| [Actions A5] | [Actions A5] | [Actions A5] | [Actions A5] |
+----------------+-----------------+------------------+----------------+
Note that for reasons of compactness, AssTrDes(S,G,I) is used in the state machine table to refer to AssertTrackingDesired(S,G,I). Same thing, just shorter so the table doesn't explode. ๐
Terminology โจ
A "preferred assert" is one with a better metric than the current winner. Simple, iconic, no notes.
An "acceptable assert" is one that has a better metric than my_assert_metric(S,G,I). An assert is never considered acceptable if its metric is infinite, so infinite metrics are automatically out of the running. ๐ซ
An "inferior assert" is one with a worse metric than my_assert_metric(S,G,I). An assert is never considered inferior if my_assert_metric(S,G,I) is infinite. Basically, if your own metric is infinite, nothing can be "worse" than you. Main character energy ๐
The state machine uses the following macros:
CouldAssert(S,G,I) =
SPTbit(S,G)==TRUE
AND (RPF_interface(S) != I)
AND (I in ( ( joins(*,G) (-) prunes(S,G,rpt) )
(+) ( pim_include(*,G) (-) pim_exclude(S,G) )
(-) lost_assert(*,G)
(+) joins(S,G) (+) pim_include(S,G) ) )
CouldAssert(S,G,I) is true for downstream interfaces that would be in the inherited_olist(S,G) if the (S,G) assert information was not taken into account. So it's basically asking "would this interface be forwarding if we ignored asserts?" ๐ฆ
AssertTrackingDesired(S,G,I) =
(I in ( joins(*,G) (-) prunes(S,G,rpt)
(+) ( pim_include(*,G) (-) pim_exclude(S,G) )
(-) lost_assert(*,G)
(+) joins(S,G) ) )
OR (local_receiver_include(S,G,I) == TRUE
AND (I_am_DR(I) OR (AssertWinner(S,G,I) == me)))
OR ((RPF_interface(S) == I) AND (JoinDesired(S,G) == TRUE))
OR ((RPF_interface(RP(G)) == I) AND (JoinDesired(*,G) == TRUE)
AND (SPTbit(S,G) == FALSE))
AssertTrackingDesired(S,G,I) is true on any interface where an (S,G) assert might affect the router's behavior on that interface. It's the "do I actually care about asserts here?" check. ๐
The first three lines of AssertTrackingDesired account for (*,G) join and local membership information received on I that might cause the router to be interested in asserts on I. Bestie, this is the group-level stuff.
The 4th line accounts for (S,G) join information received on I that might cause the router to be interested in asserts on I. The source-specific version of the same idea. ๐ธ
The 5th and 6th lines account for (S,G) local membership information on I. Here's the spicy bit: we can't use the pim_include(S,G) macro, since it uses lost_assert(S,G,I). That would make the router forget it lost an assert if the only reason it cared was local membership, and that would be a mess. ๐ The AssertWinner(S,G,I) check forces an assert winner to keep being responsible for forwarding as long as local receivers are present. Remove that check and the assert winner would give up forwarding the second the info that made it forward went away, and then forwarding for local receivers would just bounce back to the DR. Nobody wants that drama.
The last three lines account for the fact that a router has to keep track of assert information on upstream interfaces so it can send joins and prunes to the proper neighbor. Upstream needs love too ๐ซถ
Transitions from NoInfo State โจ
When in NoInfo state, the following events may trigger transitions:
Receive Inferior Assert with RPTbit cleared An assert is received for (S,G) with the RPT bit cleared that is inferior to our own assert metric. The RPT bit cleared means the sender of the assert had (S,G) forwarding state on this interface. If the assert is inferior to our metric, then we must also have (S,G) forwarding state (CouldAssert(S,G,I)==TRUE), because (S,G) asserts have priority over (*,G) asserts, so we should be the assert winner. We transition to the "I am Assert Winner" state and perform Actions A1 (below). Winner winner ๐
Receive Assert with RPTbit set AND CouldAssert(S,G,I)==TRUE An assert is received for (S,G) on I with the RPT bit set, so it's a (*,G) assert. CouldAssert(S,G,I) is TRUE only if we have (S,G) forwarding state on this interface, so we should be the assert winner. We transition to the "I am Assert Winner" state and perform Actions A1 (below). โจ
An (S,G) data packet arrives on interface I, AND CouldAssert(S,G,I)==TRUE An (S,G) data packet arrived on a downstream interface that is in our (S,G) outgoing interface list. We optimistically assume we'll be the assert winner for this (S,G), so we transition to the "I am Assert Winner" state and perform Actions A1 (below), which kicks off the assert negotiation for (S,G). Optimism is a whole vibe, honestly ๐
Receive Acceptable Assert with RPT bit clear AND AssertTrackingDesired(S,G,I)==TRUE We're interested in (S,G) asserts, either because I is a downstream interface where we have (S,G) or (*,G) forwarding state, or because I is the upstream interface for S and we have (S,G) forwarding state. The received assert has a better metric than ours, so we don't win the assert. We transition to "I am Assert Loser" and perform Actions A6 (below). Sorry bestie, not this time ๐
Transitions from "I am Assert Winner" State โจ
When in "I am Assert Winner" state, the following events trigger transitions:
Assert Timer Expires The (S,G) Assert Timer expires. As we're in the Winner state, we must still have (S,G) forwarding state that's actively being kept alive. We resend the (S,G) Assert and restart the Assert Timer (Actions A3 below). Also, the assert winner's Assert Timer is engineered to expire shortly before the timers on assert losers. This stops unnecessary thrashing of the forwarder and periodic flooding of duplicate packets. Good planning, honestly ๐ง
Receive Inferior Assert We receive an (S,G) assert or (*,G) assert mentioning S that has a worse metric than ours. Whoever sent it is in error, so we resend an (S,G) Assert and restart the Assert Timer (Actions A3 below). Not our fault, not our problem ๐
Receive Preferred Assert We receive an (S,G) assert that has a better metric than ours. We transition to "I am Assert Loser" state and perform Actions A2 (below). Heads up: this may affect the value of JoinDesired(S,G) and PruneDesired(S,G,rpt), which could cause transitions in the upstream (S,G) or (S,G,rpt) state machines. One change ripples everywhere, babe ๐
CouldAssert(S,G,I) -> FALSE Our (S,G) forwarding state or RPF interface changed in a way that makes CouldAssert(S,G,I) false. We can't perform the actions of the assert winner anymore, so we transition to NoInfo state and perform Actions A4 (below). This includes sending a "canceling assert" with an infinite metric. It's giving "I'm out" ๐ช
Transitions from "I am Assert Loser" State โจ
When in "I am Assert Loser" state, the following transitions can occur:
Receive Preferred Assert We receive an assert that's better than the current assert winner's. We stay in Loser state and perform Actions A2 below. Still losing, but with better energy โจ
Receive Acceptable Assert with RPTbit clear from Current Winner We receive an assert from the current assert winner that's better than our own metric for this (S,G). (The metric might be worse than the winner's previous metric, though.) We stay in Loser state and perform Actions A2 below. ๐
Receive Inferior Assert or Assert Cancel from Current Winner We receive an assert from the current assert winner that's worse than our own metric for this group. This is usually because the winner's metric got worse or because it's an assert cancel. We transition to NoInfo state, deleting the (S,G) assert information and letting the normal PIM Join/Prune mechanisms do their thing. Usually we'll eventually re-assert and win once data packets from S start flowing again. The comeback arc is real ๐ฅ
Assert Timer Expires The (S,G) Assert Timer expires. We transition to NoInfo state, deleting the (S,G) assert information (Actions A5 below). Timer said bye ๐
Current Winner's GenID Changes or NLT Expires The Neighbor Liveness Timer associated with the current winner expires, or we receive a Hello message from the current winner reporting a different GenID from the one it reported before. This means the current winner's interface or router has gone down (and maybe came back up), so we have to assume it no longer knows it was the winner. We transition to NoInfo state, deleting this (S,G) assert information (Actions A5 below). Reboot energy, no thoughts ๐ฅ
AssertTrackingDesired(S,G,I)->FALSE AssertTrackingDesired(S,G,I) becomes FALSE. Our forwarding state changed so that (S,G) asserts on interface I aren't of interest to us anymore. We transition to NoInfo state, deleting the (S,G) assert information. Not interested, not invested ๐
My metric becomes better than the assert winner's metric my_assert_metric(S,G,I) changed so that our assert metric for (S,G) is now better than the metric we have stored for the current assert winner. This might happen when the underlying routing metric changes, or when CouldAssert(S,G,I) becomes true, for example when SPTbit(S,G) becomes true. We transition to NoInfo state, delete this (S,G) assert state (Actions A5 below), and let the normal PIM Join/Prune mechanisms operate. Usually we'll eventually re-assert and win once data packets from S start flowing again. Plot twist, we're the main character now ๐
RPF_interface(S) stops being interface I Interface I used to be the RPF interface for S, and now it isn't. We transition to NoInfo state, deleting this (S,G) assert state (Actions A5 below). The RPF interface moved on, and so did we ๐ฆ
Receive Join(S,G) on Interface I ๐ okay so this one is a plot twist. We get a Join(S,G) whose Upstream Neighbor Address field is set to my primary IP address on interface I ๐. The move is to transition to NoInfo state, delete this (S,G) assert state (Actions A5 below โจ), and let the normal PIM Join/Prune mechanisms take over. If whoever sent that Join messed up, the normal assert mechanism will eventually re-apply and we'll lose the assert again ๐. BUT whoever sent the assert might know the previous assert winner has died, so we could end up being the new forwarder ๐. Plot armor, fr fr.
(S,G) Assert State Machine Actions
- A1: Send
Assert(S,G). Set Assert Timer to(Assert_Time - Assert_Override_Interval). Store self asAssertWinner(S,G,I). Storespt_assert_metric(S,I)asAssertWinnerMetric(S,G,I). ๐ฆ - A2: Store the new assert winner as
AssertWinner(S,G,I)and the assert winner metric asAssertWinnerMetric(S,G,I). Set Assert Timer toAssert_Time. ๐ - A3: Send
Assert(S,G). Set Assert Timer to(Assert_Time - Assert_Override_Interval). ๐ธ - A4: Send
AssertCancel(S,G). Delete assert information (AssertWinner(S,G,I)andAssertWinnerMetric(S,G,I)will then return to their default values). ๐ - A5: Delete assert information (
AssertWinner(S,G,I)andAssertWinnerMetric(S,G,I)will then return to their default values). ๐ง - A6: Store the new assert winner as
AssertWinner(S,G,I)and the assert winner metric asAssertWinnerMetric(S,G,I). Set Assert Timer toAssert_Time. If (I isRPF_interface(S)) AND (UpstreamJPState(S,G) == Joined), setSPTbit(S,G)to TRUE. โญ
Note that some of these actions may cause the value of JoinDesired(S,G), PruneDesired(S,G,rpt), or RPF'(S,G) to change, which could cause further transitions in other state machines. It's giving domino effect ๐ซถ.
4.6.2. (*,G) Assert Message State Machine โจ
The (*,G) Assert state machine for interface I is shown in Figure 9. ๐ There are three states, bestie:
- NoInfo (NI): This router has no (*,G) assert state on interface I. Totally clean slate ๐.
- I am Assert Winner (W): This router has won a (*,G) assert on interface I. It is now responsible for forwarding traffic destined for G onto interface I, with the exception of traffic for which it has (S,G) "I am Assert Loser" state. Irrespective of whether it is the DR for I, it is also responsible for handling the membership requests for G from local hosts on I. Main character energy ๐.
- I am Assert Loser (L): This router has lost a (*,G) assert on interface I. It must not forward packets for G onto interface I, with the exception of traffic from sources for which it has (S,G) "I am Assert Winner" state. If it is the DR, it is no longer responsible for handling the membership requests for group G from local hosts on I. Oof ๐ญ.
In addition, there is also an Assert Timer (AT) that is used to time out asserts on the assert losers and to resend asserts on the assert winner. โฐ Basically the timekeeper of this whole situation ๐.
When an Assert message is received with a source address other than zero, a PIM implementation must first match it against the possible events in the (S,G) assert state machine and process any transitions and actions, before considering whether the Assert message matches against the (*,G) assert state machine. Order of operations, periodt ๐ .
It is important to note that NO TRANSITION CAN OCCUR in the (,G) state machine as a result of receiving an Assert message unless the (S,G) assert state machine for the relevant S and G is in the "NoInfo" state after the (S,G) state machine has processed the message. Also, NO TRANSITION CAN OCCUR in the (,G) state machine as a result of receiving an assert message if that message triggers any change of state in the (S,G) state machine. Obviously, when the source address in the received message is set to zero, an (S,G) state machine for the S and G does not exist and can be assumed to be in the "NoInfo" state. ๐ Don't skip this part, it's literally the key.
For example, if both the (S,G) and (,G) assert state machines are in the NoInfo state when an Assert message arrives, and the message causes the (S,G) state machine to transition to either "W" or "L" state, then the assert will not be processed by the (,G) assert state machine. Lowkey counterintuitive but iconic ๐ฆ.
Another example: if the (S,G) assert state machine is in "L" state when an assert message is received, and the assert metric in the message is worse than my_assert_metric(S,G,I), then the (S,G) assert state machine will transition to NoInfo state. In such a case, if the (*,G) assert state machine were in NoInfo state, it might appear that it would transition to "W" state, but this is not the case because this message already triggered a transition in the (S,G) assert state machine. Not me thinking I'm the winner when I'm not ๐.
Figure 9: Per-Interface (,G) Assert State Machine* ๐ธ
+----------------------------------------------------------------------+
| In NoInfo (NI) State |
+-----------------------+-----------------------+----------------------+
| Receive Inferior | Data arrives for G | Receive Acceptable |
| Assert with RPTbit | on I and | Assert with RPTbit |
| set and | CouldAssert | set and AssTrDes |
| CouldAssert(*,G,I) | (*,G,I) | (*,G,I) |
+-----------------------+-----------------------+----------------------+
| -> W state | -> W state | -> L state |
| [Actions A1] | [Actions A1] | [Actions A2] |
+-----------------------+-----------------------+----------------------+
+---------------------------------------------------------------------+
| In I Am Assert Winner (W) State |
+----------------+-----------------+-----------------+----------------+
| Assert Timer | Receive | Receive | CouldAssert |
| Expires | Inferior | Preferred | (*,G,I) -> |
| | Assert | Assert | FALSE |
+----------------+-----------------+-----------------+----------------+
| -> W state | -> W state | -> L state | -> NI state |
| [Actions A3] | [Actions A3] | [Actions A2] | [Actions A4] |
+----------------+-----------------+-----------------+----------------+
+---------------------------------------------------------------------+
| In I Am Assert Loser (L) State |
+-------------+-------------+-------------+-------------+-------------+
|Receive |Receive |Receive |Assert Timer |Current |
|Preferred |Acceptable |Inferior |Expires |Winner's |
|Assert with |Assert from |Assert or | |GenID |
|RPTbit set |Current |Assert | |Changes or |
| |Winner with |Cancel from | |NLT Expires |
| |RPTbit set |Current | | |
| | |Winner | | |
+-------------+-------------+-------------+-------------+-------------+
|-> L state |-> L state |-> NI state |-> NI state |-> NI state |
|[Actions A2] |[Actions A2] |[Actions A5] |[Actions A5] |[Actions A5] |
+-------------+-------------+-------------+-------------+-------------+
+----------------------------------------------------------------------+
| In I Am Assert Loser (L) State |
+----------------+----------------+-----------------+------------------+
| AssTrDes | my_metric -> | RPF_interface | Receive |
| (*,G,I) -> | better than | (RP(G)) stops | Join(*,G) on |
| FALSE | Winner's | being I | Interface I |
| | metric | | |
+----------------+----------------+-----------------+------------------+
| -> NI state | -> NI state | -> NI state | -> NI State |
| [Actions A5] | [Actions A5] | [Actions A5] | [Actions A5] |
okay so the state machine has some macros, and they are giving main character energy ๐
CouldAssert(*,G,I) =
( I in ( joins(*,G) (+) pim_include(*,G)) )
AND (RPF_interface(RP(G)) != I)
CouldAssert(*,G,I) is true on downstream interfaces where we have (*,G) join state, or where local members asked for any traffic destined for G ๐ซถ
AssertTrackingDesired(*,G,I) =
CouldAssert(*,G,I)
OR (local_receiver_include(*,G,I)==TRUE
AND (I_am_DR(I) OR AssertWinner(*,G,I) == me))
OR (RPF_interface(RP(G)) == I AND RPTJoinDesired(G))
AssertTrackingDesired(*,G,I) is true on any interface where a (*,G) assert could change how the router behaves on that interface. It's the "yes, we care about this assert" check ๐
For compactness, the state machine table just writes AssTrDes(*,G,I) instead of the full AssertTrackingDesired(*,G,I). Same energy, shorter name โจ
Terminology ๐
- A "preferred assert" is one with a better metric than the current winner.
- An "acceptable assert" is one with a better metric than
my_assert_metric(*,G,I). An assert is never acceptable if its metric is infinite. - An "inferior assert" is one with a worse metric than
my_assert_metric(*,G,I). An assert is never inferior ifmy_assert_metric(*,G,I)is infinite.
Transitions from NoInfo State ๐ธ
These events only trigger a transition if the (S,G) assert state machine is in NoInfo state both before and after the message is considered:
- Receive Inferior Assert with RPTbit set AND CouldAssert(,G,I)==TRUE: an inferior (,G) assert shows up for G on interface I. If
CouldAssert(*,G,I)is TRUE, then I is our downstream interface and we have (*,G) forwarding state on it, so we should be the assert winner. We transition to the "I am Assert Winner" state and perform Actions A1 (below). - A data packet destined for G arrives on interface I, AND CouldAssert(,G,I)==TRUE: a data packet for G arrived on a downstream interface in our (,G) outgoing interface list. We believe we should be the forwarder for this (*,G), so we transition to "I am Assert Winner" and perform Actions A1 (below).
- Receive Acceptable Assert with RPT bit set AND AssertTrackingDesired(,G,I)==TRUE: we care about (,G) asserts, either because I is a downstream interface where we have (,G) forwarding state, or because I is the upstream interface for RP(G) and we have (,G) forwarding state. We get a (*,G) assert with a better metric than ours, so we don't win. We transition to "I am Assert Loser" and perform Actions A2 (below).
Transitions from "I am Assert Winner" State ๐
When we're in this state, these events trigger transitions, again only if the (S,G) assert state machine is in NoInfo state before and after the message:
- Receive Inferior Assert: we receive a (,G) assert with a worse metric than ours. Whoever sent it lost, so we resend a (,G) Assert and restart the Assert Timer (Actions A3 below).
- Receive Preferred Assert: we receive a (*,G) assert with a better metric than ours. We transition to "I am Assert Loser" and perform Actions A2 (below).
In this state, these events also trigger transitions:
- Assert Timer Expires: the (,G) Assert Timer expires. Since we're the Winner, we must still have (,G) forwarding state that's actively being kept alive. To stop the forwarder from thrashing and to avoid periodically flooding duplicate packets, we resend the (*,G) Assert and restart the Assert Timer (Actions A3 below).
- CouldAssert(,G,I) -> FALSE: our (,G) forwarding state or RPF interface changed so that
CouldAssert(*,G,I)became false. We can't act as the assert winner anymore, so we transition to NoInfo state and perform Actions A4 (below).
Transitions from "I am Assert Loser" State ๐ฅ
When we're in this state, these events trigger transitions, but only if the (S,G) assert state machine is in NoInfo state before and after the message:
- Receive Preferred Assert with RPTbit set: we receive a (*,G) assert better than the current assert winner's. We stay in Loser state and perform Actions A2 (below).
- Receive Acceptable Assert from Current Winner with RPTbit set: we receive a (*,G) assert from the current winner that's better than our own metric for this group, even if it's worse than the winner's previous metric. We stay in Loser state and perform Actions A2 (below).
- Receive Inferior Assert or Assert Cancel from Current Winner: we receive an assert from the current winner that's worse than our own metric for this group. This usually means the winner's metric got worse, or it's now an assert cancel. We transition to NoInfo state, delete this (*,G) assert state (Actions A5), and let the normal PIM Join/Prune mechanisms run again. Usually we'll eventually re-assert and win once data packets for G start flowing again ๐
In this state, these events also trigger transitions:
- Assert Timer Expires: the (,G) Assert Timer expires. We transition to NoInfo state and delete this (,G) assert information (Actions A5).
- Current Winner's GenID Changes or NLT Expires: the Neighbor Liveness Timer for the current winner expires, or we get a Hello from the current winner reporting a different GenID than before. That means the winner's interface or router went down (and maybe came back up), so we have to assume it no longer knows it was the winner. We transition to NoInfo state and delete the (*,G) assert information (Actions A5).
- AssertTrackingDesired(,G,I)->FALSE:
AssertTrackingDesired(*,G,I)becomes FALSE. Our forwarding state changed so (,G) asserts on interface I no longer matter to us. We transition to NoInfo state and delete this (*,G) assert information (Actions A5). - My metric becomes better than the assert winner's metric: our routing metric,
rpt_assert_metric(G,I), changed so our assert metric for (,G) is now better than the one stored for the current winner. We transition to NoInfo state, delete this (,G) assert state (Actions A5), and let the normal PIM Join/Prune mechanisms run. Usually we'll eventually re-assert and win once data packets for G start flowing again ๐ฆ - RPF_interface(RP(G)) stops being interface I: interface I used to be the RPF interface for RP(G), and now it isn't. We transition to NoInfo state and delete this (*,G) assert state (Actions A5).
- Receive Join(,G) on interface I: we receive a Join(,G) whose Upstream Neighbor Address field is set to my primary IP address on interface I. We transition to NoInfo state, delete this (*,G) assert state (Actions A5), and let the normal PIM Join/Prune mechanisms run. If whoever sent the Join made a mistake, the normal assert mechanism will eventually apply again and we'll lose the assert again. But the sender may know the previous assert winner has died, so we might end up being the new forwarder ๐ง
(*,G) Assert State Machine Actions ๐ฌ
- A1: Send Assert(,G). Set the Assert Timer to (Assert_Time - Assert_Override_Interval). Store self as AssertWinner(,G,I). Store rpt_assert_metric(G,I) as AssertWinnerMetric(*,G,I).
- A2: Store the new assert winner as AssertWinner(,G,I) and the assert winner metric as AssertWinnerMetric(,G,I). Set the Assert Timer to Assert_Time.
- A3: Send Assert(*,G). Set the Assert Timer to (Assert_Time - Assert_Override_Interval).
- A4: Send AssertCancel(,G). Delete assert information. AssertWinner(,G,I) and AssertWinnerMetric(*,G,I) then go back to their default values.
- A5: Delete assert information. AssertWinner(,G,I) and AssertWinnerMetric(,G,I) go back to their default values.
Heads up: some of these actions can change the value of JoinDesired(,G) or RPF'(,G), which can set off transitions in other state machines. Chain reaction, bestie ๐
4.6.3. Assert Metrics โจ
Assert metrics are defined like this:
struct assert_metric {
rpt_bit_flag;
metric_preference;
route_metric;
ip_address;
};
When we compare assert_metrics, the rpt_bit_flag, metric_preference, and route_metric fields are compared in order, and the first lower value wins ๐ If all three are equal, the primary IP address of the router that sent the Assert message breaks the tie, and the highest IP address wins.
To figure out the assert metric for (S,G) that goes in (or gets compared against) an Assert message sent on interface I, use this pseudocode:
assert_metric
my_assert_metric(S,G,I) {
if( CouldAssert(S,G,I) == TRUE ) {
return spt_assert_metric(S,I)
} else if( CouldAssert(*,G,I) == TRUE ) {
return rpt_assert_metric(G,I)
} else {
return infinite_assert_metric()
}
}
spt_assert_metric(S,I) is the assert metric we use when we're sending an assert based on active (S,G) forwarding state:
assert_metric
spt_assert_metric(S,I) {
return {0,MRIB.pref(S),MRIB.metric(S),my_ip_address(I)}
}
rpt_assert_metric(G,I) is the assert metric we use when we're sending an assert based only on (*,G) forwarding state:
assert_metric
rpt_assert_metric(G,I) {
return {1,MRIB.pref(RP(G)),MRIB.metric(RP(G)),my_ip_address(I)}
}
MRIB.pref(X) and MRIB.metric(X) are the routing preference and routing metric for the route to a particular (unicast) destination X, as the MRIB determines them. my_ip_address(I) is just the router's primary IP address associated with the local interface I ๐ธ
infinite_assert_metric() is the assert metric the router uses for an Assert that matches neither (S,G) nor (*,G) forwarding state:
assert_metric
infinite_assert_metric() {
return {1,infinity,infinity,0}
}
4.6.4. AssertCancel Messages ๐
okay so an AssertCancel message is literally just an RPT Assert message but with an infinite metric ๐ญ. The assert winner sends it when it deletes the forwarding state that caused the assert to happen in the first place. Other routers see that metric and it's giving "you're gone, bestie" ๐, so any router that still has forwarding state sends its own assert and takes over forwarding ๐ฅ.
An AssertCancel(S,G) is an infinite metric assert with the RPT bit set that names S as the source.
An AssertCancel(*,G) is an infinite metric assert with the RPT bit set and the source set to zero.
AssertCancel messages are just an optimization, ngl ๐ง. The original Assert timeout mechanism will let a subnet get consistent eventually. The AssertCancel mechanism just makes convergence way faster. No special processing is required for an AssertCancel message, since it's just an Assert message from the current winner. iconic, right? โจ
4.6.5. Assert State Macros ๐
The macros lost_assert(S,G,rpt,I), lost_assert(S,G,I), and lost_assert(*,G,I) get used in the olist computations of Section 4.1, and they're defined like this ๐
bool lost_assert(S,G,rpt,I) {
if ( RPF_interface(RP(G)) == I OR
( RPF_interface(S) == I AND SPTbit(S,G) == TRUE ) ) {
return FALSE
} else {
return ( AssertWinner(S,G,I) != NULL AND
AssertWinner(S,G,I) != me )
}
}
bool lost_assert(S,G,I) {
if ( RPF_interface(S) == I ) {
return FALSE
} else {
return ( AssertWinner(S,G,I) != NULL AND
AssertWinner(S,G,I) != me AND
(AssertWinnerMetric(S,G,I) is better
than spt_assert_metric(S,I) )
}
}
Note: The term "AssertWinnerMetric(S,G,I) is better than spt_assert_metric(S,I)" is required to correctly handle the transition phase when a router has (S,G) join state but has not yet set the SPTbit. In this case, it needs to ignore the assert state if it will win the assert once the SPTbit is set. this part is lowkey tricky but so important ๐ซถ
bool lost_assert(*,G,I) {
if ( RPF_interface(RP(G)) == I ) {
return FALSE
} else {
return ( AssertWinner(*,G,I) != NULL AND
AssertWinner(*,G,I) != me )
}
}
AssertWinner(S,G,I) is the IP source address of the Assert(S,G) packet that won an Assert.
AssertWinner(,G,I) is the IP source address of the Assert(,G) packet that won an Assert.
AssertWinnerMetric(S,G,I) is the Assert metric of the Assert(S,G) packet that won an Assert.
AssertWinnerMetric(,G,I) is the Assert metric of the Assert(,G) packet that won an Assert.
AssertWinner(S,G,I) defaults to NULL and AssertWinnerMetric(S,G,I) defaults to Infinity when in the NoInfo state.
Summary of Assert Rules and Rationale โจ
okay, this bit is the cheat sheet ๐. It summarizes the key rules for sending and reacting to asserts, plus the reasoning behind them. This section is not intended to be and should not be treated as a definitive specification of protocol behavior. The state machines and pseudocode should be consulted for that purpose. Instead, this section is meant to document the important aspects of Assert protocol behavior and to give helpful info for understanding and implementing this part of the protocol. ๐ธ
- Behavior: Downstream neighbors send Join(*,G) and Join(S,G) periodic messages to the appropriate RPF' neighbor, i.e., the RPF neighbor as modified by the assert process. They are not always sent to the RPF neighbor as indicated by the MRIB. Normal suppression and override rules apply.
Rationale: By sending the periodic and triggered Join messages to the RPF' neighbor instead of the RPF neighbor, the downstream router avoids re-triggering the Assert process with every Join. A side effect of sending Joins to the Assert winner is that traffic will not switch back to the "normal" RPF neighbor until the Assert times out. This will not happen until data stops flowing, if item 8, below, is implemented.
- Behavior: The assert winner for (,G) acts as the local DR for (,G) on behalf of IGMP/MLD members.
Rationale: This is required to allow a single router to merge PIM and IGMP/MLD joins and leaves. Without this, overrides don't work. ๐
- Behavior: The assert winner for (S,G) acts as the local DR for (S,G) on behalf of IGMPv3 members.
Rationale: Same rationale as for item 2.
- Behavior: (S,G) and (*,G) prune overrides are sent to the RPF' neighbor and not to the regular RPF neighbor.
Rationale: Same rationale as for item 1.
- Behavior: An (S,G,rpt) prune override is not sent (at all) if RPF'(S,G,rpt) != RPF'(*,G).
Rationale: This avoids keeping state alive on the (S,G) tree when only (,G) downstream members are left. Also, it avoids sending (S,G,rpt) joins to a router that is not on the (,G) tree. This behavior might be confusing, although this specification does indicate that such a join SHOULD be dropped. ๐ค
- Behavior: An assert loser that receives a Join(S,G) with an Upstream Neighbor Address that is its primary IP address on that interface expires the (S,G) Assert Timer.
Rationale: This is necessary in order to have rapid convergence in the event that the downstream router that initially sent a join to the prior Assert winner has undergone a topology change.
- Behavior: An assert loser that receives a Join(,G) with an Upstream Neighbor Address that is its primary IP address on that interface expires the (,G) Assert Timer and all (S,G) assert timers that do not have corresponding Prune(S,G,rpt) messages in the compound Join/Prune message.
Rationale: Same rationale as for item 6.
- Behavior: An assert winner for (,G) or (S,G) sends a canceling assert when it is about to stop forwarding on a (,G) or an (S,G) entry. This behavior does not apply to (S,G,rpt).
Rationale: This allows switching back to the shared tree after the last SPT router on the LAN leaves. Doing this prevents downstream routers on the shared tree from keeping SPT state alive. ๐
- Behavior: Resend the assert messages before timing out an assert. (This behavior is optional.)
Rationale: This prevents the periodic duplicates that would otherwise occur each time that an assert times out and is then re-established.
- Behavior: When RPF'(S,G,rpt) changes to be the same as RPF'(,G), we need to trigger a Join(S,G,rpt) to RPF'(,G).
Rationale: This allows switching back to the RPT after the last SPT member leaves. ๐
4.7. PIM Bootstrap and RP Discovery ๐ฆ
okay, big one incoming ๐ซถ. For correct operation, every PIM router within a PIM domain must be able to map a particular multicast group address to the same RP. If this is not the case, black holes may appear, where some receivers in the domain cannot receive some groups. A domain in this context is a contiguous set of routers that all implement PIM and are configured to operate within a common boundary.
A notable exception is when a PIM domain is broken up into multiple administrative scope regions. These are regions where a border has been configured so that a range of multicast groups will not be forwarded across that border. For more information on Administratively Scoped IP Multicast, see RFC 2365. The modified criteria for admin-scoped regions are that the region is convex with respect to forwarding based on the MRIB, and that all PIM routers within the scope region map scoped groups to the same RP within that region.
This specification does not mandate the use of a single mechanism to give routers the information needed to perform the group-to-RP mapping. Currently, four mechanisms are possible, and all four have associated problems ๐ญ:
- Static Configuration A PIM router MUST support the static configuration of group-to-RP mappings. Such a mechanism is not robust to failures but does at least provide a basic interoperability mechanism.
- Embedded-RP Embedded-RP defines an address allocation policy in which the address of the Rendezvous Point (RP) is encoded in an IPv6 multicast group address [16].
- Cisco's Auto-RP Auto-RP uses a PIM Dense-Mode (PIM-DM) multicast group to announce group-to-RP mappings from a central location. This mechanism is not useful if PIM Dense Mode is not being run in parallel with PIM Sparse Mode; it was only intended for use with PIM Sparse Mode Version 1. No standard specification currently exists.
- Bootstrap Router (BSR) RFC 2362 specifies a bootstrap mechanism based on the automatic election of a BSR. Any router in the domain that is configured to be a possible RP reports its candidacy to the BSR, and then a domain-wide flooding mechanism distributes the BSR's chosen set of RPs throughout the domain. As specified in RFC 2362, the BSR mechanism is flawed in its handling of admin-scoped regions that are smaller than a PIM domain, but the mechanism does work for global-scoped groups.
As far as PIM-SM is concerned, the only important requirement is that all routers in the domain (or admin scope zone for scoped regions) receive the same set of group-range-to-RP mappings. This may be achieved through any of these mechanisms, or through alternative mechanisms not currently specified. ๐
It must be operationally ensured that any RP address configured, learned, or advertised is reachable from all routers in the PIM domain. no cap, this is the part people skip and then wonder why their multicast is ghosting them ๐ป
4.7.1. Group-to-RP Mapping
bestie, okay so this is where the router picks its RP ๐ Using one of the mechanisms from earlier, a PIM router ends up with one or more possible group-range-to-RP mappings. Each mapping says which range of multicast groups (written as a group and a mask) goes to which RP. A mapping can also have a priority attached. You can end up with several mappings that all match the same group, and that's the normal situation with the BSR mechanism ๐ Here's the algorithm, step by step:
- Perform longest match on group range to obtain a list of RPs.
- From this list of matching RPs, find the ones with highest priority.
Eliminate any RPs from the list that have lower priorities.
- If only one RP remains in the list, use that RP.
- If multiple RPs are in the list, use the PIM hash function to choose one.
So if two or more mappings cover the same group, the one with the longest mask wins ๐ If the mask lengths are tied, the highest priority wins. And if there's still a tie on both mask length and priority, the hash function (see Section 4.7.2) picks the RP. It's giving tiebreaker drama ๐ญ
This algorithm gets run by a DR whenever it needs an RP for a group it doesn't know the RP for yet. That could be when a packet shows up, or when an IGMP/MLD membership indication comes in for that group.
The mapping function also gets run by all routers whenever they receive a (*,G) Join/Prune message.
Also, if the set of possible group-range-to-RP mappings changes, every router has to check whether any of its existing groups are affected. That might make a DR or acting DR re-join a group, or make it restart register encapsulation to the new RP. Not me being dramatic, this is literally how it works ๐
Implementation note: The bootstrap mechanism described in RFC 2362 omitted step 1 above. However, of the implementations we are aware of, approximately half performed step 1 anyway. Note that implementations of BSR that omit step 1 will not correctly interoperate with implementations of this specification when used with the BSR mechanism described in [11].
4.7.2. Hash Function
The hash function is used by all routers within a domain to map a group to one of the RPs from the matching set of group-range-to-RP mappings. (That set all has the same longest mask length and the same highest priority.) The algorithm takes the group address and the addresses of the candidate RPs from the mappings as input, and gives one RP address as output ๐ฆ
The protocol requires that all routers hash to the same RP within a domain (except for transients). The following hash function must be used in each router:
- For RP addresses in the matching group-range-to-RP mappings, compute a value:
Value(G,M,C(i))=
(1103515245 * ((1103515245 * (G&M)+12345) XOR C(i)) + 12345) mod 2^31
where C(i) is the RP address and M is a hash-mask. If BSR is being used, the hash-mask is given in the Bootstrap messages. If BSR is not being used, the alternative mechanism that supplies the group-range-to-RP mappings may supply the value, or else it defaults to a mask with the most significant 30 bits being one for IPv4 and the most significant 126 bits being one for IPv6. The hash-mask allows a small number of consecutive groups (e.g., 4) to always hash to the same RP. For instance, hierarchically encoded data can be sent on consecutive group addresses to get the same delay and fate-sharing characteristics. โจ
For address families other than IPv4, a 32-bit digest to be used as C(i) and G must first be derived from the actual RP or group address. Such a digest method must be used consistently throughout the PIM domain. For IPv6 addresses, it is RECOMMENDED to use the equivalent IPv4 address for an IPv4-compatible address, and the exclusive-or of each 32-bit segment of the address for all other IPv6 addresses. For example, the digest of the IPv6 address 3ffe:b00:c18:1::10 would be computed as 0x3ffe0b00 ^ 0x0c180001 ^ 0x00000000 ^ 0x00000010, where the '^' symbol represents the exclusive-or operation. ๐ง
- The candidate RP with the highest resulting hash value is then the RP chosen by this hash function. If more than one RP has the same highest hash value, the RP with the highest IP address is chosen.
4.8. Source-Specific Multicast
The Source-Specific Multicast (SSM) service model [6] can be implemented with a strict subset of the PIM-SM protocol mechanisms. Both regular IP Multicast and SSM semantics can coexist on a single router, and both can be implemented using the PIM-SM protocol. A range of multicast addresses, currently 232.0.0.0/8 in IPv4 and ff3x::/32 for IPv6, is reserved for SSM. The choice of semantics is determined by the multicast group address in both data packets and PIM messages. So the address alone decides the vibe ๐
4.8.1. Protocol Modifications for SSM Destination Addresses
The following rules override the normal PIM-SM behavior for a multicast address G in the SSM range:
- A router MUST NOT send a (*,G) Join/Prune message for any reason.
- A router MUST NOT send an (S,G,rpt) Join/Prune message for any reason.
- A router MUST NOT send a Register message for any packet that is destined to an SSM address.
- A router MUST NOT forward packets based on (,G) or (S,G,rpt) state. The (,G)- and (S,G,rpt)-related state summarization macros are NULL for any SSM address, for the purposes of packet forwarding.
- A router acting as an RP MUST NOT forward any Register-encapsulated packet that has an SSM destination address and SHOULD respond with a Register-Stop message to such a Register message.
- A router MAY optimize out the creation and maintenance of (S,G,rpt) and (*,G) state for SSM destination addresses -- this state is not needed for SSM packets.
The last three rules are there to deal with SSM-unaware "legacy" routers that might be sending (*,G) and (S,G,rpt) Join/Prunes, or Register messages, for SSM destination addresses. Note that this specification does not try to help an SSM-unaware "legacy" router with SSM operations ๐ โโ๏ธ
4.8.2. PIM-SSM-Only Routers
An implementer may choose to implement only the subset of PIM Sparse Mode that provides SSM forwarding semantics. Lowkey a big time-saver ๐ธ
A PIM-SSM-only router MUST implement the following portions of this specification:
- Upstream (S,G) state machine (Section 4.5.5)
- Downstream (S,G) state machine (Section 4.5.2)
- (S,G) Assert state machine (Section 4.6.1)
- Hello messages, neighbor discovery, and DR election (Section 4.3)
- Packet forwarding rules (Section 4.2)
A PIM-SSM-only router does not need to implement the following protocol elements:
- Register state machine (Section 4.4)
- (*,G) and (S,G,rpt) downstream state machines (Sections 4.5.1 and 4.5.3)
- (*,G) and (S,G,rpt) upstream state machines (Sections 4.5.4, 4.5.6, and 4.5.7)
- (*,G) Assert state machine (Section 4.6.2)
- Bootstrap RP election (Section 4.7)
- Keepalive Timer
- SPTbit (Section 4.2.2)
The Keepalive Timer should be treated as always running, and the SPTbit should be treated as always being set for an SSM address. On top of that, the packet forwarding rules of Section 4.2 can be simplified in a PIM-SSM-only router:
oiflist = NULL
if( iif == RPF_interface(S) AND UpstreamJPState(S,G) == Joined ) {
oiflist = inherited_olist(S,G)
} else if( iif is in inherited_olist(S,G) ) {
send Assert(S,G) on iif
}
oiflist = oiflist (-) iif
forward packet on all interfaces in oiflist
This is basically just the normal PIM-SM forwarding rule with all the (S,G,rpt) and (*,G) clauses swapped out for NULL. Simple, clean, iconic ๐
4.9. PIM Packet Formats
This section describes the details of the packet formats for PIM control messages. Let's get into the bits ๐ซถ
All PIM control messages have IP protocol number 103.
PIM messages are either unicast (e.g., Registers and Register-Stop) or multicast with TTL 1 to the 'ALL-PIM-ROUTERS' group (e.g., Join/Prune, Asserts). The source address used for unicast messages is a domain-wide reachable address. The source address used for multicast messages is the link-local address of the interface on which the message is being sent.
The IPv4 'ALL-PIM-ROUTERS' group is '224.0.0.13'. The IPv6 'ALL-PIM-ROUTERS' group is 'ff02::d'.
The PIM header common to all PIM messages is:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|PIM Ver| Type | Reserved | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
PIM Ver PIM Version number is 2.
Type Types for specific PIM messages. PIM Types are:
Message Type Destination
---------------------------------------------------------------------
0 = Hello Multicast to ALL-PIM-ROUTERS
1 = Register Unicast to RP
2 = Register-Stop Unicast to source of Register
packet
3 = Join/Prune Multicast to ALL-PIM-ROUTERS
4 = Bootstrap Multicast to ALL-PIM-ROUTERS
5 = Assert Multicast to ALL-PIM-ROUTERS
6 = Graft (used in PIM-DM only) Unicast to RPF'(S)
7 = Graft-Ack (used in PIM-DM only) Unicast to source of Graft
packet
8 = Candidate-RP-Advertisement Unicast to Domain's BSR
Reserved Set to zero on transmission. Ignored upon receipt.
Checksum The checksum is a standard IP checksum, i.e., the 16-bit one's complement of the one's complement sum of the entire PIM message, excluding the "Multicast data packet" section of the Register message. For computing the checksum, the checksum field is zeroed. If the packet's length is not an integral number of 16-bit words, the packet is padded with a trailing byte of zero before performing the checksum.
For IPv6, the checksum also includes the IPv6 "pseudo-header", as specified in RFC 2460, Section 8.1 [5]. This "pseudo-header" is prepended to the PIM header for the purposes of calculating the checksum. The "Upper-Layer Packet Length" in the pseudo-header is set to the length of the PIM message, except in Register messages where it is set to the length of the PIM register header (8). The Next Header value used in the pseudo-header is 103.
If a message is received with an unrecognized PIM Ver or Type field, or if a message's destination does not correspond to the table above, the message MUST be discarded, and an error message SHOULD be logged to the administrator in a rate-limited manner. โ ๏ธ Discard it, log it, don't spam the admin ๐
Bestie, that last rule is a whole vibe check: unknown Ver or Type, or the wrong destination, means the message gets discarded and the admin gets a rate-limited error log. Don't skip it ๐
4.9.1. Encoded Source and Group Address Formats ๐
okay so this is where PIM stops being vague and starts being very specific about how addresses get packed up. bestie, the vibe is "every bit has a job" ๐
Encoded Unicast Address ๐ฆ
An encoded unicast address takes the following format:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Addr Family | Encoding Type | Unicast Address
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+...
Addr Family The PIM address family of the 'Unicast Address' field of this address. โจ
Values 0-127 are as assigned by the IANA for Internet Address Families in [7]. Values 128-250 are reserved to be assigned by the IANA for PIM-specific Address Families. Values 251 through 255 are designated for Private Use. As there is no assignment authority for this space, collisions should be expected. ๐ญ
Encoding Type The type of encoding used within a specific Address Family. The value '0' is reserved for this field and represents the native encoding of the Address Family. ๐ธ
Unicast Address The unicast address as represented by the given Address Family and Encoding Type. ๐ง
Encoded Group Address ๐
Encoded group addresses take the following format:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Addr Family | Encoding Type |B| Reserved |Z| Mask Len |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Group multicast Address
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+...
Addr Family Described above.
Encoding Type Described above.
[B]idirectional PIM Indicates that the group range uses Bidirectional PIM [13]. For PIM-SM as defined in this specification, this bit MUST be zero. ๐
Reserved Transmitted as zero. Ignored upon receipt.
Admin Scope [Z]one Indicates that the group range is an admin scope zone. This is used in the Bootstrap Router mechanism [11] only. For all other purposes, this bit is set to zero and ignored on receipt. ๐
Mask Len The Mask length field is 8 bits. The value is the number of contiguous one bits that are left-justified and used as a mask; when combined with the group address, it describes a range of groups. It is less than or equal to the address length in bits for the given Address Family and Encoding Type. If the message is sent for a single group, then the Mask length must equal the address length in bits for the given Address Family and Encoding Type (e.g., 32 for IPv4 native encoding, 128 for IPv6 native encoding). ๐ฅ
Group multicast Address Contains the group address. ๐
Encoded Source Address ๐ซถ
An encoded source address takes the following format:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Addr Family | Encoding Type | Rsrvd |S|W|R| Mask Len |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Addr Family Described above.
Encoding Type Described above.
Reserved Transmitted as zero, ignored on receipt.
S The Sparse bit is a 1-bit value, set to 1 for PIM-SM. It is used for PIM Version 1 compatibility. ๐ฆ
W The WC (or WildCard) bit is a 1-bit value for use with PIM Join/Prune messages (see Section 4.9.5.1). ๐ธ
R The RPT (or Rendezvous Point Tree) bit is a 1-bit value for use with PIM Join/Prune messages (see Section 4.9.5.1). If the WC bit is 1, the RPT bit MUST be 1. โจ
Mask Len The mask length field is 8 bits. The value is the number of contiguous one bits that are left-justified and used as a mask; when combined with the source address, it describes a source subnet. The mask length MUST be equal to the mask length in bits for the given Address Family and Encoding Type (32 for IPv4 native and 128 for IPv6 native). A router SHOULD ignore any messages received with any other mask length. ๐
Source Address The source address. ๐ง
4.9.2. Hello Message Format ๐
A Hello message is sent periodically by routers on all interfaces. It's giving "I'm still here, bestie" ๐ซถ
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|PIM Ver| Type | Reserved | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| OptionType | OptionLength |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| OptionValue |
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| . |
| . |
| . |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| OptionType | OptionLength |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| OptionValue |
| ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
PIM Version, Type, Reserved, Checksum Described in Section 4.9. ๐
OptionType The type of the option given in the following OptionValue field. ๐
OptionLength The length of the OptionValue field in bytes. ๐
OptionValue A variable-length field, carrying the value of the option. ๐
The Option fields may contain the following values:
- OptionType 1: Holdtime โฐ
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 1 | Length = 2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Holdtime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Holdtime is the amount of time a receiver must keep the neighbor reachable, in seconds. If the Holdtime is set to '0xffff', the receiver of this message never times out the neighbor. This may be used with dial-on-demand links, to avoid keeping the link up with periodic Hello messages. ๐ฆ
An implementation MAY provide a configuration mechanism to reject a Hello message with holdtime 0xffff, and/or provide a mechanism to remove a neighbor. ๐ธ
Hello messages with a Holdtime value set to '0' are also sent by a router on an interface about to go down or changing IP address (see Section 4.3.1). These are effectively goodbye messages, and the receiving routers SHOULD immediately time out the neighbor information for the sender. ๐
- OptionType 2: LAN Prune Delay ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 2 | Length = 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|T| Propagation_Delay | Override_Interval |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The LAN Prune Delay option is used to tune the prune propagation delay on multi-access LANs. The T bit specifies the ability of the sending router to disable Join suppression. Propagation_Delay and Override_Interval are time intervals in units of milliseconds. A router originating a LAN Prune Delay option on interface I sets the Propagation_Delay field to the configured value of Propagation_Delay(I) and the value of the Override_Interval field to the value of Override_Interval(I). On a receiving router, the values of the fields are used to tune the value of the Effective_Override_Interval(I) and its derived timer values. ๐ง
Section 4.3.3 describes how these values affect the behavior of a router. โจ
- OptionTypes 3 through 16: Reserved; to be defined in future versions of this document. ๐ฎ
- OptionType 18: Deprecated and should not be used. ๐ซ
- OptionType 19: DR Priority ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 19 | Length = 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| DR Priority |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
DR Priority is a 32-bit unsigned number and should be considered in the DR election as described in Section 4.3.2. ๐
- OptionType 20: Generation ID ๐ฒ
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 20 | Length = 4 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Generation ID |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Generation ID is a random 32-bit value for the interface on which the Hello message is sent. The Generation ID is regenerated whenever PIM forwarding is started or restarted on the interface. ๐
- OptionType 24: Address List ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Type = 24 | Length = <Variable> |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Secondary Address 1 (Encoded-Unicast format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
...
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Secondary Address N (Encoded-Unicast format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The contents of the Address List Hello option are described in Section 4.3.4. All addresses within a single Address List must belong to the same address family. ๐บ๏ธ
OptionTypes 17 through 65000 are assigned by the IANA. OptionTypes 65001 through 65535 are reserved for Private Use, as defined in [9]. ๐
Unknown options MUST be ignored and MUST NOT prevent a neighbor relationship from being formed. The Holdtime option MUST be implemented; the DR Priority and Generation ID options SHOULD be implemented. The Address List option MUST be implemented for IPv6. ๐จ this part is non-negotiable, bestie.
4.9.3. Register Message Format โจ
okay so a Register message is what the DR sends to the RP when a multicast packet needs to go down the RP-tree ๐ธ the IP source address is the DR's address, and the destination is the RP's address ๐ซถ the IP TTL on the PIM packet is the system's normal unicast TTL ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|PIM Ver| Type | Reserved | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|B|N| Reserved2 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
. Multicast data packet .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- PIM Version, Type, Reserved, Checksum ๐ง these are described in Section 4.9. the checksum for Registers is a bit special though ๐ to keep the encapsulation overhead down, it's only computed over the first 8 bytes of the packet (the PIM header plus the next 4 bytes), and it skips the data packet portion. for interoperability, a message carrying a checksum calculated over the entire PIM Register message should also be accepted ๐ซถ and when you're calculating the checksum, the IPv6 pseudo-header "Upper-Layer Packet Length" is set to 8 ๐ฅ
- B the Border bit ๐ this spec deprecates it, so a router MUST set the B bit to 0 on transmission and MUST ignore this bit on reception ๐
- N the Null-Register bit ๐ a DR sets this to 1 when it's probing the RP before its local Register-Suppression Timer expires, and sets it to 0 otherwise โจ
- Reserved2 transmitted as zero, ignored on receipt ๐
- Multicast data packet this is the original packet the source sent ๐ it has to be the same address family as the encapsulating PIM packet, so an IPv6 data packet must be encapsulated in an IPv6 PIM packet ๐ the TTL of the original packet gets decremented before encapsulation, just like any other forwarded packet ๐ฆ and then the RP decrements the TTL again after decapsulating, before it forwards the packet down the shared tree ๐
For (S,G) Null-Registers, the Multicast data packet portion has a dummy IP header with S as the source address and G as the destination address ๐ when you're making an IPv4 Null-Register, the fields in that dummy IPv4 header SHOULD be filled in according to this table. any other IPv4 header fields can be any value that's valid for that field ๐ธ
Field Value
---------------------------------------
IP Version 4
Header Length 5
Checksum Header checksum
Fragmentation offset 0
More Fragments 0
Total Length 20
IP Protocol 103 (PIM)
on receipt of an (S,G) Null-Register, if the Header Checksum field is non-zero, the recipient SHOULD check the checksum and discard Null-Registers with a bad checksum ๐ซถ the recipient SHOULD NOT check the value of any individual fields, because a correct IP header checksum is enough ๐ and if the Header Checksum field is zero, the recipient MUST NOT check the checksum ๐ญ
with IPv6, an implementation generates a dummy IP header followed by a dummy PIM header, using the values in this table, plus the source and group ๐ง other IPv6 header fields can be any valid value for that field โจ
Header Field Value
--------------------------------------
IP Version 6
Next Header 103 (PIM)
Length 4
PIM Version 0
PIM Type 0
PIM Reserved 0
PIM Checksum PIM checksum, including
IPv6 "pseudo-header";
see Section 4.9
and on receipt of an IPv6 (S,G) Null-Register, if the dummy PIM header is present, the recipient SHOULD check the checksum and discard Null-Registers with a bad checksum ๐ฅ
4.9.4. Register-Stop Message Format ๐ฆ
okay this one is the reply ๐ a Register-Stop is unicast from the RP back to the sender of the Register message ๐ซถ its IP source address is the address the register was sent to, and its IP destination address is the source address of the register message ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|PIM Ver| Type | Reserved | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Group Address (Encoded-Group format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address (Encoded-Unicast format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- PIM Version, Type, Reserved, Checksum ๐ described in Section 4.9 โจ
- Group Address ๐ this is the group address from the multicast data packet in the Register ๐ its format is described in Section 4.9.1 ๐ heads up, for Register-Stops the Mask Len field holds the full address length * 8 (so 32 for IPv4 native encoding) if the message is sent for a single group ๐
- Source Address ๐ง this is the host address of the source from the multicast data packet in the register ๐ฆ the format is the encoded unicast address from Section 4.9.1 ๐ซถ and an address field of all zeros is a special wildcard that means any source ๐ธ
4.9.5. Join/Prune Message Format โจ
okay so the Join/Prune message is the one routers send upstream toward sources and RPs ๐ก ๐ซถ Joins build the shared trees (RP trees) or the source trees (SPT), and Prunes tear down source trees when members leave groups, plus sources that just aren't using the shared tree anymore ๐ it's giving "build it, then clean it up" ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|PIM Ver| Type | Reserved | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Upstream Neighbor Address (Encoded-Unicast format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Reserved | Num groups | Holdtime |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Multicast Group Address 1 (Encoded-Group format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Joined Sources | Number of Pruned Sources |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Joined Source Address 1 (Encoded-Source format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| . |
| . |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Joined Source Address n (Encoded-Source format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Pruned Source Address 1 (Encoded-Source format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| . |
| . |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Pruned Source Address n (Encoded-Source format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| . |
| . |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Multicast Group Address m (Encoded-Group format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Number of Joined Sources | Number of Pruned Sources |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Joined Source Address 1 (Encoded-Source format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| . |
| . |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Joined Source Address n (Encoded-Source format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Pruned Source Address 1 (Encoded-Source format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| . |
| . |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Pruned Source Address n (Encoded-Source format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
PIM Version, Type, Reserved, Checksum: described in Section 4.9. ๐ง
Unicast Upstream Neighbor Address: the primary address of the upstream neighbor that's the target of the message ๐ฏ its format is in the encoded unicast address in Section 4.9.1.
Reserved: transmitted as zero, and ignored on receipt ๐ค
Holdtime: the amount of time a receiver MUST keep the Join/Prune state alive, in seconds โณ if the Holdtime is set to 0xffff, the receiver SHOULD hold the state until it's canceled by the matching canceling Join/Prune message, or timed out according to local policy. this is lowkey useful for dial-on-demand links, since it stops the link from staying up just to handle periodic Join/Prune messages ๐
Note that theHoldTimeMUST be larger than theJ/P_Override_Interval(I). ๐จ
Number of Groups: the number of multicast group sets contained in the message ๐
Multicast group address: for the format, check Section 4.9.1 ๐ฆ
Number of Joined Sources: the number of joined source addresses listed for a given group ๐
Joined Source Address 1 .. n: this list has the sources for a given group that the sending router will forward multicast datagrams from, if they arrive on the interface where the Join/Prune message is sent ๐ธ see Section 4.9.1 for the encoded source address format.
Number of Pruned Sources: the number of pruned source addresses listed for a group ๐
Pruned Source Address 1 .. n: this list has the sources for a given group that the sending router does not want to forward multicast datagrams from, when they arrive on the interface where the Join/Prune message is sent ๐
Within one PIM Join/Prune message, all the Multicast Group addresses, Joined Source addresses, and Pruned Source addresses MUST be of the same address family. it is not permitted to mix IPv4 and IPv6 addresses in the same message, no exceptions ๐ โโ๏ธ also, the address family of the fields in the message SHOULD be the same as the IP source and destination addresses of the packet. this gives maximum implementation flexibility for dual-stack IPv4/IPv6 routers, which is so iconic of it ๐ and if a router gets a message with mixed family addresses, it SHOULD only process the addresses that are the same family as the unicast upstream neighbor address. periodt โจ
4.9.5.1. Group Set Source List Rules
okay bestie, so Join/Prune messages are basically built out of one or more group sets ๐ each set holds two source lists, the Joined Sources and the Pruned Sources โจ this section is the lowdown on the group set types and source list entries that can show up in one of these messages ๐ฆ
There is one valid group set type ๐
Group-Specific Set ๐ธ This is a valid IP multicast address in the group address field, with the full length of the IP address in the mask length field of the Multicast Group Address. Each Join/Prune message SHOULD NOT contain more than one group-specific set for the same IP multicast address ๐ A group-specific set may contain (*,G), (S,G,rpt), and (S,G) source list entries in the Joined or Pruned lists ๐
(*,G) ๐ This entry is used in Join/Prune messages sent towards the RP for the specified group. It expresses interest (or lack of it) in receiving traffic sent to the group through the RP shared tree. There MUST only be one such entry in both the Joined and Pruned lists of a group-specific set โจ
(*,G) entries have the Source-Address set to the address of the RP for group G, the Source-Address Mask-Len set to the full length of the IP address, and both the WC and RPT bits of the encoded-source-address set ๐ซถ
(S,G,rpt) ๐ฆ This entry is used in Join/Prune messages sent towards the RP for the specified group. It expresses interest (or lack of it) in receiving traffic through the shared tree that's sent by the specified source to this group. For each source address, the entry MUST exist in only one of the Joined and Pruned source lists of a group-specific set, but not both ๐ญ
(S,G,rpt) entries have the Source-Address set to the address of the source S, the Source-Address Mask-Len set to the full length of the IP address, and the WC bit cleared and the RPT bit set in the encoded source address ๐
(S,G) ๐ง This entry is used in Join/Prune messages sent towards the specified source. It expresses interest (or lack of it) in receiving traffic through the shortest path tree that the source sends to the specified group. For each source address, the entry MUST exist in only one of the Joined and Pruned source lists of a group-specific set, but not both ๐ฅ
(S,G) entries have the Source-Address set to the address of the source S, the Source-Address Mask-Len set to the full length of the IP address, and both the WC and RPT bits of the encoded source address cleared ๐
The rules above are enough to stop invalid combinations of source list entries in group-specific sets โจ BUT there are also a bunch of combinations that have a valid interpretation and still aren't generated by the protocol as described in this specification, so let's spill ๐
- Combining a (,G) Join and an (S,G,rpt) Join entry in the same message is redundant, because the (,G) entry already covers what the (S,G,rpt) entry says ๐
- Same deal for a (*,G) Prune and an (S,G,rpt) Prune ๐
- A (,G) Prune plus an (S,G,rpt) Join isn't generated either ๐ซ (S,G,rpt) Joins only get sent when the router is receiving all traffic for a group on the shared tree and wants to flag a change for one particular source. A (,G) Prune says the router doesn't want shared tree traffic anymore, so the (S,G,rpt) Join would be meaningless ๐ญ
- Join/Prune messages are targeted at one PIM neighbor, so including both an (S,G) Join and an (S,G,rpt) Prune in the same message is usually redundant ๐ซ The (S,G) Join already tells the neighbor the sender wants that source on the shortest path tree, so there's no need to also say it's no longer wanted on the shared tree. BUT there's a valid reading for this combo too: a downstream router might need to tell its upstream to only start forwarding a specific source once it's already getting that source on the shortest-path tree ๐ง
- An (S,G) Prune plus an (S,G,rpt) Join could maybe be used by a router to switch from getting a source on the shortest-path tree back to getting it on the shared tree (as long as the RPF neighbor is the same for both trees). HOWEVER, Sparse-Mode PIM doesn't have a mechanism for explicitly switching back to the shared tree ๐ โโ๏ธ
The rules are summarized in the table below ๐
+----------++------+-------+-----------+-----------+-------+-------+
| ||Join | Prune | Join | Prune | Join | Prune |
| ||(*,G) | (*,G) | (S,G,rpt) | (S,G,rpt) | (S,G) | (S,G) |
+----------++------+-------+-----------+-----------+-------+-------+
|Join ||- | no | ? | yes | yes | yes |
|(*,G) || | | | | | |
+----------++------+-------+-----------+-----------+-------+-------+
|Prune ||no | - | ? | ? | yes | yes |
|(*,G) || | | | | | |
+----------++------+-------+-----------+-----------+-------+-------+
|Join ||? | ? | - | no | yes | ? |
|(S,G,rpt) || | | | | | |
+----------++------+-------+-----------+-----------+-------+-------+
|Prune ||yes | ? | no | - | yes | ? |
|(S,G,rpt) || | | | | | |
+----------++------+-------+-----------+-----------+-------+-------+
|Join ||yes | yes | yes | yes | - | no |
|(S,G) || | | | | | |
+----------++------+-------+-----------+-----------+-------+-------+
|Prune ||yes | yes | ? | ? | no | - |
|(S,G) || | | | | | |
+----------++------+-------+-----------+-----------+-------+-------+
Here's how to read the legend ๐
- yes: Allowed and expected. ๐
- no: The combination isn't allowed by the protocol and MUST NOT be generated by a router. A router MAY accept these messages, but the result is undefined. An error message MAY be logged to the administrator in a rate-limited manner. ๐ค
- ?: The combination isn't expected by the protocol, but it's well defined. A router MAY accept it but SHOULD NOT generate it. ๐คทโโ๏ธ
The order of source list entries in a group set source list doesn't matter, except where the packet format itself limits it ๐
4.9.5.2. Group Set Fragmentation
okay so when a router is building a Join/Prune for a particular neighbor, it should try to pack in as much of the info it needs to pass along as it can โจ That means one group set for each multicast group with info waiting to go out, and inside each set, all the relevant source list entries ๐ซถ
On a router with a ton of multicast state, the number of entries that have to be included might make packets bigger than the maximum IP packet size ๐ฑ In most of those cases, the info can be split across multiple messages ๐งฉ
There's an exception for group sets that have a (*,G) Joined source list entry ๐ That group set says the router wants all traffic for the group on the shared tree, so it MUST include an (S,G,rpt) Pruned source list entry for every source the router does NOT want to receive. This list of (S,G,rpt) Pruned source list entries MUST NOT be split across multiple messages ๐ซ
If only N (S,G,rpt) Prune entries fit into a maximum-sized Join/Prune message, but the router has more than N (S,G,rpt) Prunes to add, then the router MUST choose to include the first N IP addresses (numerically smallest in network byte order), and the rest are ignored (not included) ๐ The smallest ones get the spotlight, the rest get left on read ๐
4.9.6. Assert Message Format โจ
okay so Assert messages are how routers settle a forwarder conflict on a link ๐ซถ like, if two routers both think they should forward the same multicast packet, that's chaos, so they send Asserts to figure out who gets to do it. A router sends one when it gets a multicast data packet on an interface where it would normally have forwarded that packet ๐ and it can also send one back in response to an Assert from another router ๐
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|PIM Ver| Type | Reserved | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Group Address (Encoded-Group format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address (Encoded-Unicast format) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|R| Metric Preference |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Metric |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Here's what each field does ๐
- PIM Version, Type, Reserved, Checksum: described in Section 4.9 ๐
- Group Address: the group address the router wants to sort out the forwarding conflict for. It's an encoded group address, as laid out in Section 4.9.1 ๐ธ
- Source Address: the source the router wants to sort out the conflict for. The source address MAY be set to zero for (*,G) asserts (more on that below) ๐ฆ The format is the encoded unicast address from Section 4.9.1.
- R: the RPTbit, a 1-bit value. It's set to 1 for Assert(*,G) messages and 0 for Assert(S,G) messages ๐ง
- Metric Preference: the preference value of the unicast routing protocol that gave the router its route to the multicast source or Rendezvous Point โจ
- Metric: the unicast routing table metric for that same route to the source or RP. It's in whatever units the unicast routing protocol uses ๐
Asserts can be sent to sort out a forwarding conflict for all traffic to a given group, or for one specific source and group, so there are two flavours ๐
- Assert(S,G): source-specific asserts, sent by routers forwarding a specific source on the shortest-path tree (SPTbit is TRUE). These have the Group-Address field set to group G and the Source-Address field set to source S. The RPTbit is set to 0, the Metric-Preference is set to
MRIB.pref(S), and the Metric is set toMRIB.metric(S)๐ - Assert(*,G): group-specific asserts, sent by routers forwarding data for the group and for the source(s) that are under contention on the shared tree. These have the Group-Address field set to group G. For data-triggered Asserts, the Source-Address field MAY be set to the IP source address of the data packet that triggered the Assert, and it's set to zero otherwise. The RPTbit is set to 1, the Metric-Preference is set to
MRIB.pref(RP(G)), and the Metric is set toMRIB.metric(RP(G))๐ฅ
4.10. PIM Timers โฐ
okay bestie, PIM-SM keeps track of a bunch of timers, which we'll get into in Section 4.11 ๐ซถ Every one of them is a countdown timer. You set it to a value, it counts down to zero, and then it usually kicks off some action ๐ Totally fine to build them as count-up timers instead (store the absolute expiry time and compare it to a real-time clock), but this spec talks about them as counting down to zero, so that's what we're doing โจ
Global Timers and per-scope timers:
Global Timers
Per interface (I):
Hello Timer: HT(I)
Per neighbor (N):
Neighbor Liveness Timer: NLT(N,I)
Per Group (G):
(*,G) Join Expiry Timer: ET(*,G,I)
(*,G) Prune-Pending Timer: PPT(*,G,I)
(*,G) Assert Timer: AT(*,G,I)
Per Source (S):
(S,G) Join Expiry Timer: ET(S,G,I)
(S,G) Prune-Pending Timer: PPT(S,G,I)
(S,G) Assert Timer: AT(S,G,I)
(S,G,rpt) Prune Expiry Timer: ET(S,G,rpt,I)
(S,G,rpt) Prune-Pending Timer: PPT(S,G,rpt,I)
Per Group (G):
(*,G) Upstream Join Timer: JT(*,G)
Per Source (S):
(S,G) Upstream Join Timer: JT(S,G)
(S,G) Keepalive Timer: KAT(S,G)
(S,G,rpt) Upstream Override Timer: OT(S,G,rpt)
At the DRs or relevant Assert Winners only:
Per Source,Group pair (S,G):
Register-Stop Timer: RST(S,G)
4.11. Timer Values ๐ง
so once timers start or restart, they get set to their default values, and this section is the cheat sheet for those defaults ๐ ๐
Heads up, though: protocol events or configuration can change a timer's default on a specific interface ๐ So when a timer gets initialized in this document, the value specific to the interface in context must be used ๐ซถ A few of the timers below (Prune-Pending, Upstream Join, Upstream Override) can be set to values that depend on the Propagation_Delay and Override_Interval settings of the matching interface, and their defaults are listed here too ๐
Variable Name: Propagation_Delay(I)
+-------------------------------+--------------+----------------------+
| Value Name | Value | Explanation |
+-------------------------------+--------------+----------------------+
| Propagation_delay_default | 0.5 secs | Expected |
| | | propagation delay |
| | | over the local |
| | | link. |
+-------------------------------+--------------+----------------------+
The default for Propagation_delay_default is picked to be on the relatively large side, so it plays nicely with older PIM implementations ๐ฆ
Variable Name: Override_Interval(I)
+--------------------------+-----------------+-------------------------+
| Value Name | Value | Explanation |
+--------------------------+-----------------+-------------------------+
| t_override_default | 2.5 secs | Default delay |
| | | interval over |
| | | which to randomize |
| | | when scheduling a |
| | | delayed Join |
| | | message. |
+--------------------------+-----------------+-------------------------+
Timer Name: Hello Timer (HT(I))
+---------------------+--------+---------------------------------------+
|Value Name | Value | Explanation |
+---------------------+--------+---------------------------------------+
|Hello_Period | 30 secs| Periodic interval for Hello messages. |
+---------------------+--------+---------------------------------------+
|Triggered_Hello_Delay| 5 secs | Randomized interval for initial Hello |
| | | message on bootup or triggered Hello |
| | | message to a rebooting neighbor. |
+---------------------+--------+---------------------------------------+
At power-up, the timer is initialized to rand(0, Triggered_Hello_Delay) so that everybody doesn't sync up at once ๐ When a new or rebooting neighbor shows up, a responding Hello goes out within rand(0, Triggered_Hello_Delay) ๐
Timer Name: Neighbor Liveness Timer (NLT(N,I))
+--------------------------+----------------------+--------------------+
| Value Name | Value | Explanation |
+--------------------------+----------------------+--------------------+
| Default_Hello_Holdtime | 3.5 * Hello_Period | Default holdtime |
| | | to keep neighbor |
| | | state alive |
+--------------------------+----------------------+--------------------+
| Hello_Holdtime | from message | Holdtime from |
| | | Hello message |
| | | Holdtime option. |
+--------------------------+----------------------+--------------------+
The Holdtime in a Hello message should be set to (3.5 * Hello_Period), which works out to a default of 105 seconds โณ
Timer Names: Expiry Timer (ET(*,G,I), ET(S,G,I), ET(S,G,rpt,I))
+----------------+----------------+------------------------------------+
| Value Name | Value | Explanation |
+----------------+----------------+------------------------------------+
| J/P_HoldTime | from message | Holdtime from Join/Prune message |
+----------------+----------------+------------------------------------+
The J/P Holdtime that goes into Join/Prune messages is covered below, in the "Upstream Join Timer (JT(*,G), JT(S,G))" description ๐ง
Timer Names: Prune-Pending Timer (PPT(*,G,I), PPT(S,G,I), PPT(S,G,rpt,I))
+--------------------------+---------------------+---------------------+
|Value Name | Value | Explanation |
+--------------------------+---------------------+---------------------+
|J/P_Override_Interval(I) | Default: | Short period after |
| | Effective_ | a join or prune to |
| | Propagation_ | allow other |
| | Delay(I) + | routers on the LAN |
| | Effective_Override_ | to override the |
| | Interval(I) | join or prune |
+--------------------------+---------------------+---------------------+
Quick note: both Effective_Propagation_Delay(I) and Effective_Override_Interval(I) are interface-specific and can change when Hello messages come in (see Section 4.3.3) ๐
Timer Names: Assert Timer (AT(*,G,I), AT(S,G,I))
+---------------------------+---------------------+--------------------+
| Value Name | Value | Explanation |
+---------------------------+---------------------+--------------------+
| Assert_Override_Interval | Default: 3 secs | Short interval |
| | | before an assert |
| | | times out where |
| | | the assert winner |
| | | resends an Assert |
| | | message |
+---------------------------+---------------------+--------------------+
| Assert_Time | Default: 180 secs | Period after last |
| | | assert before |
| | | assert state is |
| | | timed out |
+---------------------------+---------------------+--------------------+
Lowkey important: for historical reasons, the Assert message doesn't have a Holdtime field, so changing the Assert Time from its default is not recommended ๐
Timer Names: Upstream Join Timer (JT(*,G), JT(S,G))
+-------------+--------------------+-----------------------------------+
|Value Name | Value | Explanation |
+-------------+--------------------+-----------------------------------+
|t_periodic | Default: 60 secs | Period between Join/Prune messages|
+-------------+--------------------+-----------------------------------+
|t_suppressed | rand(1.1 * | Suppression period when someone |
| | t_periodic, 1.4 * | else sends a J/P message so we |
| | t_periodic) when | don't need to do so. |
| | Suppression_ | |
| | Enabled(I) is | |
| | true, 0 otherwise | |
+-------------+--------------------+-----------------------------------+
|t_override | rand(0, Effective_ | Randomized delay to prevent |
| | Override_ | response implosion when sending a |
| | Interval(I)) | Join message to override someone |
| | | else's Prune message. |
+-------------+--------------------+-----------------------------------+
okay so t_periodic is the timer that sets how often you send your Join/Prune messages, and the default is 60 secs ๐. You can tune it to fit the situation! Like, a slower link or a router in the middle of the network that expects lots of multicast route entries might want a longer period ๐ธ. If you change the Join/Prune-Period while things are running, do it pretty rarely ๐
, and keep refreshing at the old period for at least Join/Prune-Holdtime so the upstream router has time to adjust. That's just being a good bestie to your upstream neighbor ๐ซถ.
The Holdtime in a Join/Prune message should be set to (3.5 * t_periodic) โจ.
t_override depends on the Effective Override Interval of the upstream interface, and that value can change when Hello messages come in ๐. t_suppressed depends on the Suppression State of the upstream interface (Section 4.3.3), and it goes to zero when suppression is disabled ๐
โโ๏ธ.
Timer Name: Upstream Override Timer (OT(S,G,rpt))
+---------------+--------------------------+---------------------------+
| Value Name | Value | Explanation |
+---------------+--------------------------+---------------------------+
| t_override | see Upstream Join Timer | see Upstream Join Timer |
+---------------+--------------------------+---------------------------+
The Upstream Override Timer only ever gets set to the t_override value ๐ฏ. That value was defined earlier in this section, under "Timer Names: Upstream Join Timer (JT(*,G), JT(S,G))", so go check it there ๐.
Timer Name: Keepalive Timer (KAT(S,G))
+-----------------------+-----------------------+----------------------+
| Value Name | Value | Explanation |
+-----------------------+-----------------------+----------------------+
| Keepalive_Period | Default: 210 secs | Period after last |
| | | (S,G) data packet |
| | | during which (S,G) |
| | | Join state will be |
| | | maintained even in |
| | | the absence of |
| | | (S,G) Join |
| | | messages. |
+-----------------------+-----------------------+----------------------+
| RP_Keepalive_Period | ( 3 * Register_ | As |
| | Suppression_Time ) | Keepalive_Period, |
| | + Register_ | but at the RP when |
| | Probe_Time | a Register-Stop is |
| | | sent. |
+-----------------------+-----------------------+----------------------+
The normal keepalive period for the KAT(S,G) defaults to 210 seconds ๐. But at the RP, the keepalive period has to be at least the Register_Suppression_Time, or the RP might time out the (S,G) state before the next Null-Register shows up ๐ฑ. So the KAT(S,G) gets set to max(Keepalive_Period, RP_Keepalive_Period) whenever a Register-Stop is sent ๐.
Timer Name: Register-Stop Timer (RST(S,G))
+---------------------------+--------------------+---------------------+
|Value Name | Value | Explanation |
+---------------------------+--------------------+---------------------+
|Register_Suppression_Time | Default: 60 secs | Period during |
| | | which a DR stops |
| | | sending Register- |
| | | encapsulated data |
| | | to the RP after |
| | | receiving a |
| | | Register-Stop |
| | | message. |
+---------------------------+--------------------+---------------------+
|Register_Probe_Time | Default: 5 secs | Time before RST |
| | | expires when a DR |
| | | may send a Null- |
| | | Register to the RP |
| | | to cause it to |
| | | resend a Register- |
| | | Stop message. |
+---------------------------+--------------------+---------------------+
If the Register_Suppression_Time or the Register_Probe_Time gets configured to something other than the defaults, it MUST be ensured that the Register_Probe_Time is less than half the value of the Register_Suppression_Time ๐จ. That keeps the Register-Stop Timer from ending up with a possible negative value ๐.
5. IANA Considerations ๐
5.1. PIM Address Family ๐
The PIM Address Family field was made 8 bits as a tradeoff between packet format and using the IANA-assigned numbers ๐งฎ. When the PIM packet format was designed, only 15 Address Family values were assigned, and nobody pictured large numbers of new ones, so 8 bits seemed big enough ๐. But the IANA assigns Address Families in a 16-bit field, so the PIM Address Family gets allocated like this ๐:
- Values 0 through 127 are designated to have the same meaning as IANA-assigned Address Family Numbers [7].
- Values 128 through 250 are designated to be assigned for PIM by the IANA based upon IESG Approval, as defined in [9].
- Values 251 through 255 are designated for Private Use, as defined in [9].
5.2. PIM Hello Options ๐ฆ
Values 17 through 65000 are to be assigned by the IANA ๐. The space is huge, so they may be assigned as First Come First Served, as defined in [9] ๐โโ๏ธ. Those assignments are valid for one year and may be renewed ๐. Permanent assignments require a specification (see "Specification Required" in [9]) ๐.
6. Security Considerations ๐ฅ
This section covers some possible security concerns related to the PIM-SM protocol ๐จ. For more on PIM-SM and multicast security, check out [8], [14], and [15] ๐.
Note that PIM relies on an MRIB that gets populated outside of PIM ๐. So securing the sources of change to the MRIB is RECOMMENDED ๐. Please don't skip this one, bestie ๐ซถ.
6.1. Attacks Based on Forged Messages ๐
How much damage is possible depends on what kinds of counterfeit messages get accepted ๐ฌ. Next up, we look at the impact of possible forgeries, including forged link-local (Join/Prune, Hello, and Assert) messages and forged unicast (Register and Register-Stop) messages ๐ต๏ธโโ๏ธ.
6.1.1. Forged Link-Local Messages ๐
Join/Prune, Hello, and Assert messages all get sent to the link-local ALL-PIM-ROUTERS multicast address, so a compliant router doesn't forward them ๐ซ. A forged message of this type can only reach a LAN if a local host sent it, or if a compromised or non-compliant router let it onto the LAN ๐ค.
- A forged Join/Prune message can cause multicast traffic to be delivered to links where there are no legitimate requesters, which could waste bandwidth on that link ๐ธ. A forged leave message on a multi-access LAN usually isn't a big deal in PIM, because any legitimately joined router on the LAN would override the leave with a join before the upstream router stops forwarding data to the LAN ๐.
- By forging a Hello message, an unauthorized router can get itself elected as the Designated Router on a LAN ๐. The Designated Router on a LAN is (in the absence of asserts) responsible for forwarding traffic to that LAN on behalf of any local members ๐ฆ. It's also responsible for register-encapsulating to the RP any packets that hosts on the LAN originate ๐. So a forged Hello message can mess with local hosts' ability to send and receive multicast traffic ๐ญ.
- By forging an Assert message on a multi-access LAN, an attacker could make the legitimate designated forwarder stop forwarding traffic to the LAN ๐. That kind of forgery would stop any hosts downstream of that LAN from receiving traffic ๐.
6.1.2. Forged Unicast Messages ๐ง
Register messages and Register-Stop messages get forwarded by intermediate routers to their destination using normal IP forwarding ๐ฌ. Without data origin authentication, an attacker located anywhere in the network may be able to forge a Register or Register-Stop message ๐ฅท. Here's what a forgery of each one could do ๐.
- By forging a Register message, an attacker can cause the RP to inject forged traffic onto the shared multicast tree ๐ณ.
- By forging a Register-Stop message, an attacker can stop a legitimate DR from registering packets to the RP ๐ซ. That can keep local hosts on that LAN from sending multicast packets ๐ต.
Intermediate routers don't change the two PIM messages above, so they only need to be examined by the intended receiver ๐ฏ. That means these messages can be authenticated end-to-end โ . Attacks on Register and Register-Stop messages don't apply to a PIM-SSM-only implementation, since those messages aren't required for PIM-SSM ๐ฆโจ.
6.2. Non-cryptographic Authentication Mechanisms ๐
okay so this one is the lowkey-weak sibling ๐ฅบ but we still gotta do what we can, bestie. A PIM router SHOULD provide an option to limit the set of neighbors it will accept Join/Prune, Assert, and Hello messages from. Static configuration of IP addresses MAY be used for this, or an IPsec security association MAY be used instead. Furthermore, a PIM router SHOULD NOT accept protocol messages from a router it hasn't yet gotten a valid Hello message from. no cap, that's basic hygiene ๐
A Designated Router MUST NOT register-encapsulate a packet and send it to the RP unless the source address of that packet is a legal address for the subnet the packet was received on. Similarly, a Designated Router SHOULD NOT accept a Register-Stop packet whose IP source address isn't a valid RP address for the local domain. the way the DR has to double-check the source before doing anything is so iconic ๐ญ๐ซถ
An implementation SHOULD provide a mechanism that lets an RP restrict the range of source addresses it accepts Register-encapsulated packets from. main character energy for the RP, honestly ๐
All options that restrict the range of addresses packets are accepted from MUST default to allowing all packets. so by default nobody's getting shut out until someone actually turns the restriction on ๐ธ
6.3. Authentication ๐ฆ
this doc points at RFC 5796 [8], which is the spec that covers how to authenticate PIM-SM link-local messages using the IPsec Encapsulating Security Payload (ESP) or, optionally, the Authentication Header (AH). ngl that's the real authentication glow-up ๐ง
It also calls out that non-link-local PIM-SM messages (aka Register and Register-Stop messages) can be secured by a normal unicast IPsec Security Association (SA) between two communicants. so those get their own protection through a regular unicast SA ๐
6.4. Denial-of-Service Attacks ๐
okay this section is giving "the internet is a little scary sometimes" ๐ญ There are a number of possible denial-of-service attacks against PIM that can be caused by generating false PIM protocol messages, or even by generating false traffic. Authenticating PIM protocol traffic stops some of these attacks, but not all of them. Two of the possible attacks are:
- Sending packets to many different group addresses quickly can be a denial-of-service attack all on its own. This causes lots of register-encapsulated packets, which load up the DR, the RP, and the routers sitting between the DR and the RP.
- Forging Join messages can cause a multicast tree to get set up. A large number of forged joins can eat up router resources and lead to denial of service.
7. References ๐
7.1. Normative References ๐ฅ
[1] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997, http://www.rfc-editor.org/info/rfc2119.
[2] Cain, B., Deering, S., Kouvelas, I., Fenner, B., and A. Thyagarajan, "Internet Group Management Protocol, Version 3", RFC 3376, DOI 10.17487/RFC3376, October 2002, http://www.rfc-editor.org/info/rfc3376.
[3] Deering, S., "Host extensions for IP multicasting", STD 5, RFC 1112, DOI 10.17487/RFC1112, August 1989, http://www.rfc-editor.org/info/rfc1112.
[4] Deering, S., Fenner, W., and B. Haberman, "Multicast Listener Discovery (MLD) for IPv6", RFC 2710, DOI 10.17487/RFC2710, October 1999, http://www.rfc-editor.org/info/rfc2710.
[5] Deering, S. and R. Hinden, "Internet Protocol, Version 6 (IPv6) Specification", RFC 2460, DOI 10.17487/RFC2460, December 1998, http://www.rfc-editor.org/info/rfc2460.
[6] Holbrook, H. and B. Cain, "Source-Specific Multicast for IP", RFC 4607, DOI 10.17487/RFC4607, August 2006, http://www.rfc-editor.org/info/rfc4607.
[7] IANA, "Address Family Numbers", http://www.iana.org/assignments/address-family-numbers.
[8] Atwood, W., Islam, S., and M. Siami, "Authentication and Confidentiality in Protocol Independent Multicast Sparse Mode (PIM-SM) Link-Local Messages", RFC 5796, DOI 10.17487/RFC5796, March 2010, http://www.rfc-editor.org/info/rfc5796.
[9] Narten, T. and H. Alvestrand, "Guidelines for Writing an IANA Considerations Section in RFCs", BCP 26, RFC 5226, DOI 10.17487/RFC5226, May 2008, http://www.rfc-editor.org/info/rfc5226.
7.2. Informative References ๐
[10] Bates, T., Chandra, R., Katz, D., and Y. Rekhter, "Multiprotocol Extensions for BGP-4", RFC 4760, DOI 10.17487/RFC4760, January 2007, http://www.rfc-editor.org/info/rfc4760.
[11] Bhaskar, N., Gall, A., Lingard, J., and S. Venaas, "Bootstrap Router (BSR) Mechanism for Protocol Independent Multicast (PIM)", RFC 5059, DOI 10.17487/RFC5059, January 2008, http://www.rfc-editor.org/info/rfc5059.
[12] Black, D., "Differentiated Services and Tunnels", RFC 2983, DOI 10.17487/RFC2983, October 2000, http://www.rfc-editor.org/info/rfc2983.
[13] Handley, M., Kouvelas, I., Speakman, T., and L. Vicisano, "Bidirectional Protocol Independent Multicast (BIDIR-PIM)", RFC 5015, DOI 10.17487/RFC5015, October 2007, http://www.rfc-editor.org/info/rfc5015.
[14] Savola, P., Lehtonen, R., and D. Meyer, "Protocol Independent Multicast - Sparse Mode (PIM-SM) Multicast Routing Security Issues and Enhancements", RFC 4609, DOI 10.17487/RFC4609, October 2006, http://www.rfc-editor.org/info/rfc4609.
[15] Savola, P. and J. Lingard, "Host Threats to Protocol Independent Multicast (PIM)", RFC 5294, DOI 10.17487/RFC5294, August 2008, http://www.rfc-editor.org/info/rfc5294.
[16] Savola, P. and B. Haberman, "Embedding the Rendezvous Point (RP) Address in an IPv6 Multicast Address", RFC 3956, DOI 10.17487/RFC3956, November 2004, http://www.rfc-editor.org/info/rfc3956.
[17] Zheng, L., Zhang, J., and R. Parekh, "Survey Report on Protocol Independent Multicast - Sparse Mode (PIM-SM) Implementations and Deployments", RFC 7063, DOI 10.17487/RFC7063, December 2013, http://www.rfc-editor.org/info/rfc7063.
Appendix A. Functionality Removed from RFC 4601 ๐งน
based on a survey of PIM implementations and deployments [17] that the IETF PIM working group ran, the following functionality from RFC 4601 got removed, since there wasn't enough implementation and deployment experience for it:
(*,*,RP) StatePIM Multicast Border Router (PMBR)Authentication using IPsec
Acknowledgements ๐ซถ
PIM-SM was designed over many years by a big group of people, and they're owed so much. Thank you to Deborah Estrin, Dino Farinacci, Ahmed Helmy, David Thaler, Steve Deering, Van Jacobson, C. Liu, Puneet Sharma, Liming Wei, Tom Pusateri, Tony Ballardie, Scott Brim, Jon Crowcroft, Paul Francis, Joel Halpern, Horst Hodel, Polly Huang, Stephen Ostrowski, Lixia Zhang, Girish Chandranmenon, Brian Haberman, Hal Sandick, Mike Mroz, Garry Kump, Pavlin Radoslavov, Mike Davison, James Huang, Christopher Thomas Brown, and James Lingard for their ideas, comments, and corrections. โจ
And a special thank-you to the American Licorice Company, for its obscure but possibly essential role in the creation of this document ๐๐
Authors' Addresses ๐๏ธ
Bill Fenner Arista Networks
Email: fenner@arista.com
Mark Handley Department of Computer Science University College London Gower Street London WC1E 6BT United Kingdom
Email: M.Handley@cs.ucl.ac.uk
Hugh Holbrook Arista Networks 5453 Great America Parkway Santa Clara, CA 95054
Email: holbrook@arista.com
Isidor Kouvelas Arista Networks 5453 Great America Parkway Santa Clara, CA 95054
Email: kouvelas@arista.com
Rishabh Parekh Cisco Systems, Inc. 170 W. Tasman Drive San Jose, CA 95134
Email: riparekh@cisco.com
Zhaohui Zhang Juniper Networks 10 Technology Park Drive Westford, MA 01886
Email: zzhang@juniper.net
Lianshu Zheng Huawei Technologies Co., Ltd Huawei Campus, 156 Beiqing Road, Hai-dian District Beijing 100089 China
Email: vero.zheng@huawei.com
๐ฆ where this page comes from
- ietf: rfc7761 2016-03
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms