Re-join multicast groups when a capture re-attaches - #71
Merged
Conversation
A destroyed interface takes its group memberships with it, and Rebind only re-attached the capture. Nothing else re-joins: a family that keeps its addresses across a recreation is no transition, so the reflector's own bring-up never runs, and the capture comes back attached and permanently deaf on every group it had. Rebind now re-programs them on a join socket reopened per family. A kept socket would be refused the duplicate (group, index) join where a recreated interface is handed back its old number, and memberships it holds for a dead index still count against igmp_max_memberships. The close precedes the reopen so the descriptor it frees is the one the reopen takes. A re-join that fails leaves the capture attached, so nothing else would report the socket as deaf and the reconcile's probe would skip it; GroupsJoined is that second reason to rebind. A join reporting the membership is already held is the end state we want, not a failure; one reporting no address of the group's family yet is a wait the family teardown or the retry resolves.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Interface-recreation recovery restored the capture's binding but not what makes
traffic arrive at it. A destroyed interface takes its multicast group memberships
with it,
Rebind()only re-attached the capture, and nothing else re-joins: afamily that keeps its addresses across the recreation is no transition, so the
reflector's own bring-up never runs. The interface came back with a live capture
that received nothing for any group it had — and on a switched network that is
unambiguous, since no IGMP/MLD report goes out and upstream stops forwarding.
The fix
Rebind()now ends by re-programming every group the socket holds a membershipfor, on a join socket reopened per family.
The reopen is not tidiness. A kept socket is refused the duplicate
(group, index)join where a recreated interface is handed back the number it had, and memberships
it holds for a dead index still count against
igmp_max_memberships(20 by default,not raisable on a locked-down router) — so kept sockets would exhaust the cap after
a handful of recreations. The close precedes the reopen so the descriptor it frees
is the one the reopen takes, which is what lets the repair work at the process fd
limit rather than only when descriptors are plentiful.
GroupsJoined()is a second, independent reason to rebind. A failed re-join leavesthe capture attached, so the reconcile's probe-first early-out would skip the
socket permanently and the repair retry would never retry.
Join errnos are now classified: a membership already held is the end state we want
rather than a failure, and no address of the group's family yet is a wait — the
family's teardown drops the membership, or the retry replays the join once an
address arrives — so it logs at debug instead of once a second.
Testing
A root-gated test joins a group, recreates the interface, rebinds, and asserts the
membership against
/proc/net/igmp. Asking the kernel is the point: the socket's ownbookkeeping still claims the group after the interface behind it died, so a
seam-level assertion would pass either way.
Verified the test fails without the fix — with the re-join removed it fails on
exactly the "must be re-programmed on the interface's new kernel object" assertion,
while all 897 other tests pass. That is how this got through the first time: the
existing recreation test asserts the capture re-attaches and keeps its fd, which
stays true while the socket is deaf.
910 unit tests native (Debug, ASan/UBSan), 898 in docker including 20 root-gated.