Skip to content

pimd: add IGMP/MLD proxy route-map filtering#21906

Open
Jafaral wants to merge 11 commits into
FRRouting:masterfrom
Jafaral:igmp-proxy-filter
Open

pimd: add IGMP/MLD proxy route-map filtering#21906
Jafaral wants to merge 11 commits into
FRRouting:masterfrom
Jafaral:igmp-proxy-filter

Conversation

@Jafaral
Copy link
Copy Markdown
Member

@Jafaral Jafaral commented May 9, 2026

This PR introduces per-interface route-map filtering for the IGMP/MLD proxy feature, allowing operators to control which (S,G) groups are forwarded via proxy. It also adds a new route-map match condition for matching on the interface where the IGMP/MLD report was received.

New CLI

  ip igmp proxy route-map RMAP_NAME
  no ip igmp proxy route-map

New route-map match condition

  match multicast-source-interface IFNAME

This matches the interface where the IGMP/MLD report was received (the upstream/listener-facing interface), and is distinct from the existing match multicast-interface, which matches the proxy output interface.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 9, 2026

Greptile Summary

This PR adds per-interface route-map filtering for the IGMP/MLD proxy feature, allowing operators to control which (S,G) groups are forwarded via proxy, along with a new match multicast-source-interface route-map condition that matches the interface where the IGMP/MLD report was received.

  • Introduces ip igmp proxy route-map RMAP_NAME CLI, backed by a new proxy-route-map YANG leaf in frr-gmp.yang, a new pim_filter_ref gm_proxy_filter field on pim_interface, and northbound handlers lib_interface_gm_proxy_rmap_modify/destroy.
  • Adds the match multicast-source-interface IFNAME route-map match condition and propagates a new source_interface field through pim_rmap_info and pim_filter_match at all call sites.
  • Fixes the config-write ordering bug (proxy route-map now emitted before proxy) and moves proxy config write to the AF-generic pim_config_write path, resolving the IPv6/pim6d persistence gap.

Confidence Score: 5/5

Safe to merge; the new filter infrastructure is correctly wired into all relevant code paths and the previous config-persistence and write-ordering issues noted in earlier review rounds have been addressed.

The core implementation is sound: gm_proxy_filter is properly initialized, registered in the global filter-ref list so route-map pointer updates propagate correctly, and finalized alongside gmp_filter. The interface/source_interface arguments are ordered consistently in every pim_filter_match call site. The config write was moved to the AF-generic pim_config_write path with proxy route-map emitted before proxy, fixing both the IPv6 persistence gap and the startup ordering race. The one behavioral nuance — that the filter gates prunes as well as joins, meaning stale proxy joins survive host leaves when a filter is tightened without cycling the proxy — is documented and only arises from a deliberate operator action without following the documented cycle step.

pimd/pim_tib.c — the filter check on the prune path is worth a second look from a maintainability perspective.

Important Files Changed

Filename Overview
pimd/pim_tib.c Adds filter check on both join and prune paths in tib_sg_proxy_join_prune_check; filter on the prune path can prevent cleanup of stale proxy joins when the filter is tightened without cycling the proxy.
pimd/pim_routemap.c Adds route_match_source_interface function, pim_rmap_info.source_interface field, and updates pim_filter_match signature; VRF-scoped lookup is correct and source_interface is never NULL in all updated call sites.
pimd/pim_vty.c Removes proxy write from IPv4-only gm_config_write and adds AF-generic proxy route-map/proxy writes to pim_config_write, with correct ordering (route-map before proxy) to fix config replay issue.
pimd/pim_nb_config.c New lib_interface_gm_proxy_rmap_modify/destroy handlers follow the same pattern as lib_interface_gm_rmap_modify; correct pim_if_new fallback, filter ref set/clear, and NB_ERR_INCONSISTENCY on missing ifp in destroy.
pimd/pim_iface.c gm_proxy_filter properly init'd and fini'd alongside gmp_filter; filter is checked per-(S,G) in pim_if_gm_proxy_init with correct interface/source_interface argument order.
yang/frr-gmp.yang Adds proxy-route-map leaf with route-map-ref type; no when/must constraint requiring proxy=true, consistent with how other filter leaves are handled in FRR.
yang/frr-pim-route-map.yang Adds multicast-source-interface identity and case with interface-ref leaf; derived-from-or-self when expression is correct and consistent with existing pattern.
tests/topotests/pim_basic_igmp_proxy/test_pim_igmp_proxy.py Two new topotests (TC:6 and TC:7) cover route-map filtering and source-interface matching; both include positive and negative assertions, config persistence checks, and cleanup steps.
pimd/pim_igmpv3.c All pim_filter_match call sites updated to pass group->interface for both interface and source_interface arguments; correct for non-proxy contexts where both are the same interface.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[IGMP Report received on ifp] --> B{Is gmp_filter set?}
    B -- Yes --> C[pim_filter_match\ngmp_filter, ifp, ifp]
    B -- No --> D[Allow]
    C -- PERMIT --> D
    C -- DENY --> E[Drop/skip]

    D --> F{Is proxy enabled\non any oif?}
    F -- No --> G[Normal IGMP state update]
    F -- Yes --> H[tib_sg_proxy_join_prune_check\nfor each oif with gm_proxy]

    H --> I{gm_proxy_filter\nset on oif?}
    I -- No --> J[pim_if_gm_join_add / del]
    I -- Yes --> K[pim_filter_match\ngm_proxy_filter, oif, ifp\ninterface=oif, source_interface=ifp]
    K -- PERMIT --> J
    K -- DENY --> L[Skip join/prune]

    M[ip igmp proxy\nconfig enable] --> N[pim_if_gm_proxy_init\noif = proxy output iface]
    N --> O[For each ifp != oif\nFor each S,G in ifp group list]
    O --> P{gm_proxy_filter set?}
    P -- No --> Q[pim_if_gm_join_add]
    P -- Yes --> R[pim_filter_match\ngm_proxy_filter, oif, ifp]
    R -- PERMIT --> Q
    R -- DENY --> S[Skip]
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
pimd/pim_tib.c:97-110
**Filter gates prunes as well as joins — stale proxy joins survive host leaves after a filter is tightened**

`pim_filter_match` is evaluated for both `join=true` and `join=false` paths. In steady-state with a stable filter this is correct: a group that was never joined also never needs to be pruned. However, if the proxy is running with no filter (all groups proxied), and an operator then adds a restrictive route-map *without* cycling the proxy, the subsequent host leave for a now-denied group hits `continue` here and the proxy join is silently skipped — leaving multicast state installed on the output interface even though no downstream host is requesting it. The stale join persists until the proxy is explicitly cycled.

Applying the filter only on the join leg (`if (join && !pim_filter_match(...)) continue;`) would allow prunes to always clean up existing state, regardless of whether the filter has changed. The current behavior requires an explicit cycle to take effect, which is documented but not enforced or warned about at the CLI.

Reviews (4): Last reviewed commit: "pimd: always populate source_interface f..." | Re-trigger Greptile

Comment thread pimd/pim_vty.c Outdated
Comment thread pimd/pim_routemap.c
Comment thread pimd/pim_routemap.c Outdated
@Jafaral Jafaral force-pushed the igmp-proxy-filter branch from f072018 to 266bbac Compare May 9, 2026 04:51
@Jafaral
Copy link
Copy Markdown
Member Author

Jafaral commented May 9, 2026

@greptile review

Comment thread pimd/pim_vty.c Outdated
@Jafaral Jafaral force-pushed the igmp-proxy-filter branch from 266bbac to 57b5767 Compare May 9, 2026 05:06
@Jafaral
Copy link
Copy Markdown
Member Author

Jafaral commented May 9, 2026

@greptile review

@Jafaral Jafaral force-pushed the igmp-proxy-filter branch from 57b5767 to db15c77 Compare May 12, 2026 04:42
@Jafaral
Copy link
Copy Markdown
Member Author

Jafaral commented May 12, 2026

@greptile review

Comment thread doc/user/pim.rst
Comment thread tests/topotests/pim_basic_igmp_proxy/test_pim_igmp_proxy.py Outdated
@donaldsharp
Copy link
Copy Markdown
Member

a) In be67c41 -> this will not be bisectable since it will fail to build.:

  CC       staticd/static_nht.o
pimd/pim_tib.c: In function ‘tib_sg_proxy_join_prune_check’:
pimd/pim_tib.c:101:30: error: too few arguments to function ‘pim_filter_match’
  101 |                         if (!pim_filter_match(&pim_ifp->gm_proxy_filter, &pfx, ifp))
      |                              ^~~~~~~~~~~~~~~~
In file included from pimd/pim_instance.h:18,
                 from pimd/pim_tib.c:12:
pimd/pim_routemap.h:32:13: note: declared here
   32 | extern bool pim_filter_match(const struct pim_filter_ref *ref, const struct prefix_sg *sg,
      |             ^~~~~~~~~~~~~~~~
  CC       staticd/static_routes.o
  CC       staticd/static_zebra.o
  CC       staticd/static_vrf.o
make[1]: *** [Makefile:13988: pimd/pimd-pim_tib.o] Error 1
make[1]: *** Waiting for unfinished jobs....
  CC       staticd/static_nb.o
pimd/pim_iface.c: In function ‘pim_if_gm_proxy_init’:
pimd/pim_iface.c:1624:38: error: too few arguments to function ‘pim_filter_match’
 1624 |                                 if (!pim_filter_match(&oif_pim->gm_proxy_filter, &pfx, oif))
      |                                      ^~~~~~~~~~~~~~~~
In file included from pimd/pim_instance.h:18,
                 from pimd/pim_iface.c:22:
pimd/pim_routemap.h:32:13: note: declared here
   32 | extern bool pim_filter_match(const struct pim_filter_ref *ref, const struct prefix_sg *sg,
      |             ^~~~~~~~~~~~~~~~
pimd/pim_iface.c: In function ‘pim_if_gm_proxy_init’:
pimd/pim_iface.c:1624:38: error: too few arguments to function ‘pim_filter_match’
 1624 |                                 if (!pim_filter_match(&oif_pim->gm_proxy_filter, &pfx, oif))
      |                                      ^~~~~~~~~~~~~~~~
In file included from pimd/pim_instance.h:18,
                 from pimd/pim_iface.c:22:
pimd/pim_routemap.h:32:13: note: declared here
   32 | extern bool pim_filter_match(const struct pim_filter_ref *ref, const struct prefix_sg *sg,
      |             ^~~~~~~~~~~~~~~~
make[1]: *** [Makefile:12878: pimd/pim6d-pim_iface.o] Error 1
pimd/pim_tib.c: In function ‘tib_sg_proxy_join_prune_check’:
pimd/pim_tib.c:101:30: error: too few arguments to function ‘pim_filter_match’
  101 |                         if (!pim_filter_match(&pim_ifp->gm_proxy_filter, &pfx, ifp))
      |                              ^~~~~~~~~~~~~~~~
In file included from pimd/pim_instance.h:18,
                 from pimd/pim_tib.c:12:
pimd/pim_routemap.h:32:13: note: declared here
   32 | extern bool pim_filter_match(const struct pim_filter_ref *ref, const struct prefix_sg *sg,
      |             ^~~~~~~~~~~~~~~~
make[1]: *** [Makefile:13630: pimd/pimd-pim_iface.o] Error 1
make[1]: *** [Makefile:13236: pimd/pim6d-pim_tib.o] Error 1

Comment thread pimd/pim_tib.c
continue;

if (pim_ifp->gm_enable && pim_ifp->gm_proxy) {
struct prefix_sg pfx;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I have an igmp join already and then we add a route-map to deny the join, and then we receive a prune, this will cause the prune to be dropped on the floor, thus leaving the igmp join, correct?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also have a debug log here to show the fact it was filtered? I think every other spot has it.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I debated whether I should still allow prune always, I wasn't sure if it has any side effects on upstream-side, but I think it is safe.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed the commit/compile issue. Once of my earlier fixups landed with the wrong signature for that commit. I also now only apply the filter on joins (exclude prune). the debug log is added too.

Jafaral added 10 commits May 13, 2026 13:06
Add a new optional `proxy-route-map` leaf to the GMP interface
address-family grouping, allowing operators to filter which (S,G)
groups are forwarded via IGMP proxy.

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
Introduce a new CLI command:

```
  ip igmp proxy route-map RMAP_NAME
  no ip igmp proxy route-map
```
This wires the proxy-route-map YANG leaf into the northbound layer via
lib_interface_gm_proxy_rmap_modify/destroy callbacks, stores the
resolved route-map in a new gm_proxy_filter field, and emits the
command in the running-config output.

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
Add gm_proxy_filter (pim_filter_ref) to struct pim_interface, initialized
and torn down alongside gmp_filter.

Apply pim_filter_match() against gm_proxy_filter at both proxy join
entry points:
- pim_if_gm_proxy_init: filters groups replayed when proxy is enabled
- tib_sg_proxy_join_prune_check: filters dynamic join/prune events as
  new IGMP reports arrive

Only (S,G) groups permitted by the route-map are forwarded via proxy.

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
Add a clicmd entry for the new 'ip igmp proxy route-map ROUTE-MAP'
command, including a description of when filtering is evaluated,
the supported match conditions, and a brief configuration example.

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
Test that a route-map attached to an IGMP proxy interface correctly
filters which (S,G) groups are forwarded:
- Only groups permitted by the route-map appear after a proxy cycle
- A join for a denied group does not appear dynamically
- Removing the route-map and cycling proxy restores all groups

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
Add a new identity 'multicast-source-interface' and the corresponding
augment case, allowing route-map entries to match against the interface
where the IGMP/MLD report was received (the proxy source interface).

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
Extend pim_rmap_info with a source_interface field populated at proxy
join time, and add a new route-map match condition:

  match multicast-source-interface IFNAME

This matches the interface where the IGMP/MLD report was received (the
upstream/listener-facing interface), distinct from the existing
'match multicast-interface' which matches the proxy output interface.

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
Pass the IGMP/MLD report source interface to pim_filter_match in both
proxy join paths:
- pim_if_gm_proxy_init: pass the interface whose group list is being
  replayed (ifp) as source_interface
- tib_sg_proxy_join_prune_check: pass oif (the interface that triggered
  the join/prune event) as source_interface

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
Add documentation for the new 'match multicast-source-interface IFNAME'
route-map condition in the 'ip igmp proxy route-map' command entry,
explaining that it matches the interface where the IGMP/MLD report was
received, and include a usage example.

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
Verify that a proxy route-map using 'match multicast-source-interface'
only forwards groups reported on the matched interface:
- Apply a route-map matching r1-eth2; cycle proxy
- Verify only 225.3.3.3/225.4.4.4 (static joins on r1-eth2) are proxied
- Verify groups from r1-eth0 (225.2.2.2, 225.5.5.5, 225.7.7.7) are absent
- Remove route-map; cycle proxy; verify all five groups return

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
Non-proxy callers passed NULL, making 'match multicast-source-interface'
silently deny every (S,G) when used in a regular ip/ipv6 igmp/mld
route-map. Pass the inbound interface in all callers; proxy callers
keep their distinct listener/output values.

Signed-off-by: Jafar Al-Gharaibeh <jafar@atcorp.com>
@Jafaral Jafaral force-pushed the igmp-proxy-filter branch from db15c77 to 67893ac Compare May 13, 2026 18:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants