Skip to content

networklayer: fix: the IGMP and MLD defects that the standards tests found - #1388

Open
levy wants to merge 45 commits into
masterfrom
topic/standards-tests-igmp-mld-level2-fixes
Open

levy wants to merge 45 commits into
masterfrom
topic/standards-tests-igmp-mld-level2-fixes

Conversation

@levy

@levy levy commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

The level 2 standards tests of IGMP (tests/protocol/igmp) and MLD (tests/protocol/mld) found twelve defects in Igmpv3 and sixteen defects in Mld, against RFC 9776, RFC 9777, RFC 2236 and RFC 2710. The tests also found that two features of the RFCs are missing: the IGMPv2 and IGMPv1 modes of an IGMPv3 router, and the MLDv1 mode of an MLDv2 router. doc/project/evidence/model/igmp/results.md and mld/results.md on master list them as model gaps. On master, 17 IGMP tests and 25 MLD tests fail because of the defects, and one test of each protocol is an expected failure for the missing feature. This pull request repairs the defects and adds the two features, one gap of one protocol in each commit. After it, all 43 IGMP tests and all 47 MLD tests pass.

The defects have an effect on every simulation with IGMPv3 or MLD. Some examples:

  • Queries carried 0 in the QRV and QQIC fields. IGMP messages of a host had the wrong precedence.
  • MLD messages left from the global address and without the Router Alert option. With the option, the Payload Length of Ipv6 did not count the extension headers, so the receiver cut off the message.
  • The ICMPv6 checksum in the "computed" mode did not cover the pseudo-header.
  • An Mldv1 node stopped the simulation with an error when it received an MLDv2 Report or Query.
  • Two default intervals, the older-version modes of a host, the merge of pending State-Change Reports, the empty records, the S flag, and the retransmissions of the specific Queries did not follow the RFCs.
  • A multicast router forwarded a datagram onto a leaf interface when the interface had a listener of the group, also when that listener excluded the source. So the source lists of IGMPv3 and MLDv2 had no effect on the data.

Each repair commit carries its source change, the tests that it makes pass, its paragraph of the release note doc/whatsnew/incompatible/ipv4-igmp-and-mld-corrections.txt, and the fingerprint values that it moves. The statistical results are on the branch with the same name in the statistics repository. Most commits come in twins, IGMP first and MLD next, because the two protocols share their state machines.

The measurement found one defect in the code of master. With the Router Alert option of MLD and the computed checksums of the ~tND fingerprint runs, PimChecksumInsertionHook::datagramPostRoutingHook dereferences the protocol of the first header after the IPv6 header. For the Hop-by-Hop Options header that pointer is null, and the three IPv6 PIM examples crashed. 5e71aa2fac checks the pointer, before the Router Alert commit.

Risk: the trajectories of IGMPv3, MLD and IPv6 multicast simulations change. 38 fingerprint configurations and 7 statistical results move. The defaults of unsolicitedReportInterval (10 s to 1 s) and groupMembershipInterval (260 s to 270 s) of Igmpv3 and Mldv2 change. An MLD querier now starts only when its link-local address is tested, about 2 seconds after the start. A multicast router that uses source lists forwards less data.

Two other pull requests touch the same code:

  • The ND repair pull request, networklayer: fix: the Neighbor Discovery defects that the standards tests found #1392 (topic/standards-tests-nd-level2-fixes), is stacked on this one, and merges after it. ND needs two repairs of this branch: the MLDv1 node that ignores an MLDv2 Report, and the MLD querier that starts when its link-local address is tested. Without them, the ND repairs make five MLD tests fail on master. The solicited-node change of 547998f8da is also ND gap 9, and the ND pull request does not repeat it. On this branch the ND test Rfc4862GroupsBeforeDad still fails, for a new reason: MLD holds the Report for the solicited-node group until the link-local address is tested, so the Report leaves after the solicitation. RFC 9777 section 5.2.14 and RFC 3590 send it from the unspecified address. networklayer: fix: the Neighbor Discovery defects that the standards tests found #1392 gives the test that reason and leaves this half of ND gap 9 open.
  • icmpv6: keep one Router Discovery process per interface #1266 (icmpv6: keep one Router Discovery process per interface) changes Ipv6NeighbourDiscovery.cc, which this branch does not change. No conflict is expected.

Commits, in the order to read them

Each fix and add commit in src moves behavior. The tests: fix commits prepare the next repair: each corrects a test whose timing or count the repair changes, and changes no model code. 4c0c82f9e2 and 5e71aa2fac prepare the Router Alert commit. The two networklayer commits about source records prepare the commits of the specific Queries. The plan and doc commits change no behavior.

  • 8a74080aa3 plan: add: IGMP and MLD level 2 repairs, one gap for each commit
  • 4d3c6816a7 ipv4: fix: a Query carries the QRV and the QQIC of its querier
  • d651cca285 icmpv6: fix: a Query carries the QRV and the QQIC of its querier
  • 69b7c6c56f ipv4: fix: a Report has the precedence of Internetwork Control
  • 3df2134a49 tests: fix: the first MLD Query comes when the router is up
  • 4a2c2fb83d networklayer: fix: every MLD message leaves from a link-local address
  • 4c0c82f9e2 ipv6: fix: the Payload Length counts the requested extension headers
  • 5e71aa2fac pim: fix: the checksum hook passes a datagram with an IPv6 extension header
  • 4cded48972 networklayer: fix: every MLD message carries the Router Alert option
  • fa65fbd2de networklayer: fix: the ICMPv6 checksum covers the pseudo-header
  • bd06c77cdd icmpv6: fix: an MLDv1 node ignores an MLDv2 Report
  • 755c7d2c4c tests: fix: Rfc2710DoneInV1Mode waits for the Report for G
  • 547998f8da ipv6: fix: an interface joins its solicited-node groups
  • a54ccd1750 ipv4: fix: two default intervals take the values of RFC 9776
  • ffd6323dde icmpv6: fix: two default intervals take the values of RFC 9777
  • 078d0f2ca7 ipv4: fix: the IGMPv2 mode of a host lasts the RFC 9776 interval
  • 91374c8bfc tests: fix: the MLDv1 router of the MLD checks is up at the start
  • fd1e60e208 icmpv6: fix: the MLDv1 mode of a node lasts the RFC 9777 interval
  • 4f4b0e9375 plan: change: IGMP gaps 1 to 4 and MLD gaps 1 to 7 are repaired
  • d60cd36a8d ipv4: fix: a second change merges the pending State-Change Report
  • 8efc1c0947 icmpv6: fix: a second change merges the pending State-Change Report
  • 0b9cf51333 ipv4: fix: the IGMPv2 mode of a host delays and suppresses Reports
  • 6b5023986c tests: fix: Rfc2710ReportSuppression counts the Reports for G
  • 018fdd8d91 icmpv6: fix: the MLDv1 mode of a node delays and suppresses Reports
  • 8d7f3709fe ipv4: fix: a change of the compatibility mode cancels the timers
  • 7934cd24b3 icmpv6: fix: a change of the compatibility mode cancels the timers
  • fca5a38c38 ipv4: fix: an answer to a Query leaves out empty records
  • 4052c83303 icmpv6: fix: an answer to a Query leaves out empty records
  • ec57ce6578 ipv4: fix: the S flag comes from the lowered group timer
  • b0df60f916 icmpv6: fix: the S flag comes from the lowered Filter Timer
  • a45806b8ef networklayer: fix: the routers delete the right source records safely
  • 7c6a881dce networklayer: fix: a BLOCK record in EXCLUDE mode adds its new sources
  • aa4b0272ae ipv4: fix: the specific Queries keep the state of RFC 9776
  • d28574e02a icmpv6: fix: the specific Queries keep the state of RFC 9777
  • 8fe0629bf2 ipv4: fix: multicast forwarding asks for a listener of the source
  • 77457f21b0 ipv6: fix: multicast forwarding asks for a listener of the source
  • a945af00a5 tests: fix: Rfc9777RouterV1Listener waits for the Report of C for G
  • 81c9d9eedd icmpv6: fix: an MLDv1 node reads an MLDv2 Query
  • 9e53b90254 doc: add: IGMP check of a querier configured in IGMPv1 mode
  • 5d0424e445 ipv4: add: the IGMPv2 and IGMPv1 modes of a router
  • 5a63185f4f icmpv6: add: the MLDv1 mode of a router
  • cba2cef887 plan: change: IGMP gaps 5 to 13 and MLD gaps 8 to 17 are repaired
  • 9a5a08cd99 doc: change: IGMP and MLD results, ledgers and matrices after the repairs
  • b8794f8001 plan: change: the IGMP and MLD repairs are done
  • ccef104396 plan: location: move the IGMP and MLD repair plan to done

Architectural surface:

  • Packet content: the QRV and QQIC of IGMPv3 and MLDv2 Queries; the Type of Service of IGMP messages; the source address of MLD messages; the new Router Alert option, Ipv6RouterAlertOption in Ipv6ExtensionHeaders.msg, and its serialization; the Payload Length of a datagram with requested extension headers; the computed ICMPv6 checksum; the Queries of the IGMPv2, IGMPv1 and MLDv1 router modes.
  • Configuration surface: the new parameters routerVersion and olderHostPresentInterval of Igmpv3, and routerVersion and olderVersionHostPresentInterval of Mldv2. The two interval names follow RFC 9776 section 8.13 and RFC 9777 section 9.13. The new defaults of unsolicitedReportInterval and groupMembershipInterval.
  • Contracts: the forwarding of Ipv4 and Ipv6 onto a leaf interface now asks the existing hasMulticastListener(group, source) of the interface data, not hasMulticastListener(group). Ipv6InterfaceData emits a change signal when an address becomes tentative or valid. A multicast router delivers a multicast datagram locally when it carries the Router Alert option. Icmpv6::insertComputedChecksum() is new, and Ipv6 calls it when the source address is final.
  • Module tests: the IGMPv3 and MLD module tests follow the new defaults and timers; the MLDv2 module tests expect the solicited-node groups.
  • No feature descriptor changes, no sealed path, and no new AV-* or NV-* row.

Evidence

All builds are clean builds (make MODE=<mode> clean, then make MODE=<mode>), with OMNeT++ 6.x and clang on Linux. Incremental builds after the rebases left stale objects, which gave false crashes and false fingerprint moves. The commands are those of opp_repl, as on master.

Command Result
opp_run_protocol_tests -m debug -w '^tests/protocol/igmp$' at the tip 43 PASS (master: 42 tests, 17 FAIL, 1 expected FAIL; the new test checks the IGMPv1 mode of a router)
opp_run_protocol_tests -m debug -w '^tests/protocol/mld$' at the tip 47 PASS (master: 25 FAIL, 1 expected FAIL)
opp_run_protocol_tests -m debug at the tip 825 tests: 440 PASS, 306 expected SKIP (the packetdrill tests need inet-gpl), 45 expected FAIL, 34 FAIL. The 34 are the 25 open tests of the RIP and ND gaps, and 9 tests of tests/protocol/ipv6 (Rfc4443*, Rfc8200*), which fail in the same way with the source of master in the same build. No other test fails.
opp_run_module_tests -m debug at the tip 759 PASS
opp_run_fingerprint_tests -m release at the tip 1840 tests: 1837 PASS, 2 expected ERROR, 1 FAIL: examples/inet/gsandcs SAT, which depends on the z3 version of the machine and does not move with this branch
inet_run_statistical_tests -m release, in a copy of the container of the GitHub job (clang-16), with the statistics branch (see the note below the table) 929 tests: 841 PASS, 10 expected SKIP, 1 expected ERROR, 76 FAIL and 1 ERROR. The same 77 configurations fail on master with the statistics master (802.11, 10BASE-T1S, DHCP and other areas). No other configuration fails.

The container of the GitHub job has no opp_repl. So the statistical runs used INET's own copy of the test framework, with the commits of this branch replayed onto master 664b0f8940, before master removed that copy. The src trees are the same as on this branch. On GitHub, the job runs opp_run_statistical_tests.

Baseline updates. Each commit got its fingerprint values in tests/fingerprint/store.json from a clean release build at that commit (opp_update_fingerprint_test_results -m release -w <dirs> -i <ini files> -c <configs>, for the configurations that the branch moves). Each statistics commit was made in the container of the GitHub job at the INET commit that it names, for the configurations that the commit moves. Each commit message names the configurations.

Commit Fingerprint configurations Statistical results
4d3c6816a7 IGMPv3 QRV and QQIC 1 (~tND only) 0
d651cca285 MLDv2 QRV and QQIC 2 (~tND only) 0
69b7c6c56f IGMP precedence 1 (~tND only) 0
4a2c2fb83d MLD link-local source 5 5
4cded48972 MLD Router Alert 6 6
fa65fbd2de ICMPv6 checksum 37 (~tND only) 0
547998f8da solicited-node groups 5 5
a54ccd1750 IGMPv3 default intervals 1 1
ffd6323dde MLDv2 default intervals 2 2

The other commits move no fingerprint and no statistical result. The statistics branch is topic/standards-tests-igmp-mld-level2-fixes of inet-framework/statistics: 5 commits on its master ca95f16.

Not verified here:

  • The feature tests, the validation tests and the chart tests. The CI of this pull request runs them.
  • An open finding of the plan: Icmpv6 emits packetDropped when a checksum is wrong, but Icmpv6.ned does not declare the signal, so a debug run stops there. The branch does not make that path more reachable than before, and does not repair it.

The plan, with the decisions of each gap, is plan/done/igmp-mld-level2-fixes.md.

🤖 Generated with Claude Code

levy added 30 commits October 27, 2026 10:18
Before this commit, the branch had no plan for the repair of the gaps that the level 2 passes of IGMP and MLD found.

After this commit, the plan lists the repairs, one gap for each commit.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: plan | behavior.add | - | igmp-mld-model-repairs
Before this commit, the IGMP Queries carried no QRV and no QQIC (gap 1 of the IGMP results). Igmpv3::sendGeneralQuery set the type, the Max Resp Code and the length, and nothing else. The Group-Specific and the Group-and-Source-Specific Queries and their retransmissions were built the same way. Both fields kept the value 0 of the message class.

After this commit, the four builders of a Query call the new setQuerierFields. It puts the Robustness Variable of the querier into the QRV, and 0 when the value is above 7 (RFC 9776 section 4.1.6). It puts the code of the Query Interval into the QQIC (RFC 9776 section 4.1.7).

The QQIC uses codeTime. Its assertion for a value of 128 or more was wrong. After the shifts, the mantissa holds its implicit leading bit, so it is 16 to 31 and not at most 15. A debug run with a Query Interval of 128 s or more would have stopped.

To see the defect, run Rfc9776QueryEncapsulation and Rfc9776QueryRobustness of tests/protocol/igmp on the parent commit: they fail at observations 5 and 2.

Fingerprints: 1 configuration moves, examples/inet/igmp -c IGMPv3, in ~tND only, because of the packet data.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4.Igmpv3 | behavior.change.fix | fingerprint test whatsnew | igmp-mld-model-repairs
Before this commit, the MLD Queries carried no QRV and no QQIC (gap 1 of the MLD results). This is the twin of IGMP gap 1. The four builders of an MLDv2 Query set the type, the Maximum Response Code and the length only, so both fields were 0.

After this commit, the four builders call the new setQuerierFields. It puts the Robustness Variable of the querier into the QRV, and 0 when the value is above 7 (RFC 9777 section 5.1.8). It puts the code of the Query Interval into the QQIC (RFC 9777 section 5.1.9).

The QQIC is an 8-bit code, and Mldv2 had only the 16-bit code of the Maximum Response Code. The new codeQqic makes the 8-bit code, as Igmpv3::codeTime does.

To see the defect, run Rfc9777QueryEncapsulation and Rfc9777QueryRobustness of tests/protocol/mld on the parent commit: they fail.

Fingerprints: 2 configurations move, in ~tND only, because of the packet data: ipv6/mld -c MldV2Ssm and pim ssm_ipv6, whose MLDv2 Queries carry the new QRV and QQIC.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv2 | behavior.change.fix | fingerprint test whatsnew | igmp-mld-model-repairs
Before this commit, an IGMP Report left with precedence 0 (gap 2 of the IGMP results). Igmpv3::sendReportToIP had the line that sets the DSCP in a comment, under "TODO set Type of Service to 0xc0". Igmpv3::sendQueryToIP set it, so the Queries had it.

After this commit, the line is in effect. RFC 9776 section 4 sends every IGMP message with the precedence of Internetwork Control. The line covers every Report and Leave of an IGMPv3 host. It also covers the IGMPv2 messages of a host in IGMPv2 mode, which go through the same function.

To see the defect, run Rfc9776Precedence of tests/protocol/igmp on the parent commit: it fails at observation 3, at the first Report of A at 10 s.

Fingerprints: 1 configuration moves, examples/inet/igmp -c IGMPv3, in ~tND only, because of the Type of Service of the Reports.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4.Igmpv3 | behavior.change.fix | fingerprint test whatsnew | igmp-mld-model-repairs
Before this commit, Rfc9777StartupQueries and Rfc9777QuerierElection waited for the first General Query of a router within 1 s of the start. Their checks in router-queries.md said "within 1 second of the start" and "at the start", from RFC2710-ROUTER-6: a router that attaches to a link becomes the querier immediately. This assumed a router that is up at t = 0. A Query leaves from a link-local address (RFC9777-GEN-2). A router of the model has a tested one only when it has booted and ended its Duplicate Address Detection, up to about 2.3 s after the start. The repair of MLD gap 2 starts the querier then, and the two tests failed at their first step.

After this commit, the checks and the tests wait 3 s for the first Query, and state that the later Queries count from it. With the old code, whose first Query leaves at t = 0, both tests still pass.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: tests | behavior.change.fix | - | igmp-mld-model-repairs
Before this commit, an MLD message left from a global address (gap 2 of the MLD results). Mldv2::sendToIPv6 gave the IPv6 layer the destination, the interface and the Hop Limit, and no source. So IPv6 took the global address of the interface. Mldv1 had the same shape. RFC 2710 section 3 and RFC 9777 section 5 send every MLD message from a link-local address. A receiver drops a Query from any other (RFC 3810 section 5.1.13).

After this commit, both modules give the link-local address as the source, when Duplicate Address Detection has tested it. Before that, a node has no such address: in the model, a router gets it up to about 2.3 s after the start. So the querier of an interface starts when the interface has a tested link-local address, and a Query without one is not sent. A Report or a Done waits in the module and leaves then.

RFC 3810 section 5.2.13 would send such a Report from the unspecified address. The IPv6 layer of the model replaces an unspecified source with its preferred address, so the module waits.

The modules learn the moment from the interfaceIpv6ConfigChanged signal. Ipv6InterfaceData did not emit it when an address became tentative or valid. permanentlyAssign and tentativelyAssign emit it now.

To see the defect, run Rfc9777ReportSource, Rfc9777QuerySource and Rfc9777HopLimitAndSource of tests/protocol/mld on the parent commit: they fail. Rfc9777StartupQueries and Rfc9777QuerierElection wait for the first Query within 3 s since the commit before.

Fingerprints: 5 configurations move, in tplx, ~tNl and ~tND, the examples with MLD on: ipv6/mld (MldDemo and MldV2Ssm) and pim (dm_ipv6, sm_ipv6 and ssm_ipv6).

Statistical results: 5 results move, the examples with MLD on: ipv6/mld (MldDemo and MldV2Ssm) and pim (dm_ipv6, sm_ipv6 and ssm_ipv6). The new results are in the statistics repository, branch topic/standards-tests-igmp-mld-level2-fixes, commit "mld: update statistics after the link-local source of MLD messages".

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer | behavior.change.fix | fingerprint statistical test whatsnew | igmp-mld-model-repairs
Before this commit, Ipv6::encapsulate set the Payload Length from the length of the upper-layer message, and then inserted the extension headers of the Ipv6ExtHeaderReq tag. So the Payload Length did not count them. Ipv6::decapsulate at the receiver cut off the end of the message as padding.

After this commit, the Payload Length is set after the extension headers are in the packet. RFC 8200 section 3 counts every extension header in the Payload Length.

No module of INET asks for an extension header with the tag yet. The next commit makes every MLD message ask for a Hop-by-Hop Options header, and its test Rfc9777RouterAlert covers this repair.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv6.Ipv6 | behavior.change.fix | whatsnew | igmp-mld-model-repairs
…header

Before this commit, PimChecksumInsertionHook::datagramPostRoutingHook dereferenced the protocol of the IPv6 header to find the PIM datagrams. When the next header of a datagram is an extension header, for example the Hop-by-Hop Options header, getProtocol() returns nullptr, and the hook stopped the process with a segmentation fault. PIM installs the hook only with computed checksums (checksumMode = "computed"). No module of the model sends such a datagram yet. The next commit makes every MLD message carry a Hop-by-Hop Options header with the Router Alert option, and a PIM router then sends such messages.

After this commit, the hook accepts a datagram whose IPv6 header names no protocol, as it accepts every other datagram that is not PIM.

To see the defect, apply the next commit without this one, and run the fingerprint test of examples/pim/ssm_ipv6 with the ~tND ingredients, which compute the checksums: the run stops with a segmentation fault in datagramPostRoutingHook.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.routing.pim | behavior.change.fix | - | igmp-mld-model-repairs
Before this commit, no MLD message carried a Hop-by-Hop Options header (gap 3 of the MLD results). Mldv2::sendToIPv6 asked for no extension header, and Mldv1 had the same shape. The model had no Router Alert option for IPv6. RFC 2710 section 3 and RFC 9777 section 5 send every MLD message with the Router Alert option of RFC 2711.

After this commit, Ipv6ExtensionHeaders.msg has the option type 5 and the class Ipv6RouterAlertOption, and the serializer reads and writes it. The new createIpv6RouterAlertHeader() gives a Hop-by-Hop Options header with the option and a PadN option, 8 octets, as a real node sends it. Both MLD modules ask for this header with the Ipv6ExtHeaderReq tag, value 0 (MLD).

A multicast router delivered every ICMPv6 multicast datagram locally, to receive the MLD messages. With the Hop-by-Hop Options header, the Next Header of an MLD message is 0. So an MLDv1 Report to the address of a group did not reach the router any more. The router now delivers a multicast datagram locally when it carries the Router Alert option, which is the purpose of the option (RFC 2711 section 2). Neighbor Discovery at the router does not need the old rule, because its messages go to addresses of the node.

To see the defect, run Rfc9777RouterAlert of tests/protocol/mld on the parent commit: it fails at the first Query of R. Without the new rule of the router, Rfc2710DoneAtV1Router fails.

Fingerprints: 6 configurations move. 5 move in tplx, ~tNl and ~tND, the examples with MLD on: ipv6/mld (MldDemo and MldV2Ssm) and pim (dm_ipv6, sm_ipv6 and ssm_ipv6), whose MLD messages are 8 octets longer. The sixth is inet/ipsec -f ipv6.ini -c Multicast6, in tplx only. Its two hosts have multicastForwarding on, and each one delivered the Router Solicitation of the other to its Neighbor Discovery, which ignored it. Now the host drops it at once.

Statistical results: 6 results move. They are the examples with MLD on: ipv6/mld (MldDemo and MldV2Ssm) and pim (dm_ipv6, sm_ipv6 and ssm_ipv6), whose MLD messages are 8 octets longer. The sixth is inet/ipsec -f ipv6.ini -c Multicast6, whose hosts drop the Router Solicitations of each other at once. The new results are in the statistics repository, branch topic/standards-tests-igmp-mld-level2-fixes, commit "mld: update statistics after the Router Alert option".

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer | behavior.change.fix | fingerprint statistical test whatsnew | igmp-mld-model-repairs
Before this commit, the ICMPv6 checksum had no pseudo-header (gap 4 of the MLD results). Icmpv6::insertChecksum summed the serialized ICMPv6 message alone, and Icmpv6::verifyChecksum checked the same sum. The defect was in every ICMPv6 message of the model, Neighbor Discovery too. Two nodes of the model agreed with each other, but a real node or a capture tool rejected every message.

After this commit, the checksum covers the pseudo-header of RFC 8200 section 8.1. RFC 4443 section 2.3 requires it, and RFC 9777 sections 5.1.2 and 5.2.2 require it for MLD. The pseudo-header holds the final destination of a Routing header (RFC 8200 section 8.1) and the home address of a Home Address option (RFC 6275 section 11.3.1).

The pseudo-header needs the final source address, which only Ipv6::fragmentPostRouting knows, after the source selection and the deferral for Duplicate Address Detection. So insertChecksum leaves a computed checksum at 0, and the Ipv6 module computes it there with the new Icmpv6::insertComputedChecksum(), as UDP does in its post-routing hook. Every ICMPv6 sender is inside the IPv6 layer, so no hook is needed.

An ICMPv6 error about a local datagram does not pass the Ipv6 module. So sendErrorMessage computes its checksum with the loopback addresses of its L3AddressInd. verifyChecksum takes the addresses from L3AddressInd. Without the tag, in a dissector, it checks only the chunks.

To see the defect, run Rfc9777ReportChecksum and Rfc9777QueryChecksum of tests/protocol/mld on the parent commit: they fail.

Fingerprints: 37 configurations move, in ~tND only, which the fingerprint tool runs with checksumMode="computed". They are the IPv6 examples of that set, whose ICMPv6 checksums change.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer | behavior.change.fix | fingerprint test whatsnew | igmp-mld-model-repairs
Before this commit, Mldv1::processMldMessage read every received MLD message as an MldMessage, the base of the MLDv1 messages, and stopped the simulation on any type it did not know. An MLDv2 Report (type 143) is not an MldMessage. Its arrival stopped the run with "Cannot convert chunk from type inet::Mldv2Report to type inet::MldMessage".

After this commit, the function reads the type from the ICMPv6 header, and ignores an MLDv2 Report. An MLDv1 node does not know the type, and RFC 4443 section 2.4 (b) discards an informational message of an unknown type silently.

The next change of this branch (MLD gap 5) makes a host send Reports for its solicited-node groups at about 2 s, before the first MLDv1 Query puts it in MLDv1 mode. Without this repair, Rfc2710DoneAtV1Router, Rfc2710DoneInV1Mode and Rfc9777ListenerBackToV2 of tests/protocol/mld then stop at 2 s in the MLDv1 router.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv1 | behavior.change.fix | whatsnew | igmp-mld-model-repairs
Before this commit, the first step of the program took any MLDv1 Report of A. Observation 1 of the check "Done in MLDv1 mode" (doc/project/evidence/protocol/mld/checks/compatibility.md) is an MLDv1 Report of A for G, the stimulus. The deadline of 21 s for the Done counts from that Report. While A reported only G, the two were the same. The next change of this branch (MLD gap 5) makes A report its solicited-node groups too, at about 2 s. The first step then matched that Report, and the Done at 60 s missed the deadline at 23 s.

After this commit, the first step takes only a Report for G, as the check says. The test passes before and after this commit, and after MLD gap 5.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: tests | behavior.change.fix | - | igmp-mld-model-repairs
Before this commit, a node did not report its solicited-node addresses (gap 5 of the MLD results). The ND level 2 pass found the same cause (its gap 9). A node accepted a packet to a solicited-node address by a match against its own addresses (Ipv6InterfaceData::matchesSolicitedNodeMulticastAddress), and never joined the group. So MLD never reported it. A switch that snoops MLD would drop the solicitations of address resolution and of Duplicate Address Detection.

After this commit, Ipv6InterfaceData::assignAddress joins the group of a unicast address, and removeAddress leaves it, through the new updateSolicitedNodeGroup. There is one membership for all addresses that map to the same group. RFC 4861 section 7.2.1 joins the solicited-node group of each unicast address. RFC 9777 section 5 reports every address that a node listens to, except ff02::1 and the addresses of scope 0 and 1.

This is the change of the ND repair branch (topic/standards-tests-nd-level2-fixes, "ipv6: fix: an interface joins the solicited-node groups of its addresses"), applied again here. On this branch, a Report waits for the link-local address (MLD gap 2). So the Reports for these groups leave at about 2 s, after Duplicate Address Detection and not before it as RFC 4862 section 5.4.2 asks.

To see the defect, run Rfc9777SolicitedNodeReport of tests/protocol/mld on the parent commit: it fails at observation 5, where A sends no Report for ff02::1:ff00:a. The two commits before this one keep the MLDv1 tests running.

MLDv2_host_query and MLDv2_host_ssm list the groups of an interface and read its Current-State Reports, which now hold ff02::1:ff00:1 too. Their expected lines have that group. MLD_host_basic and MLD_host_groupstates pin a seed at which host1 wins the report race. Host1 still wins at seed 1, and their notes now list the seeds at which it wins.

Fingerprints: 5 configurations move, in tplx, ~tNl and ~tND, the examples with MLD on: ipv6/mld (MldDemo and MldV2Ssm) and pim (dm_ipv6, sm_ipv6 and ssm_ipv6).

Statistical results: 5 results move, the examples with MLD on: ipv6/mld (MldDemo and MldV2Ssm) and pim (dm_ipv6, sm_ipv6 and ssm_ipv6), whose nodes report their solicited-node groups. The new results are in the statistics repository, branch topic/standards-tests-igmp-mld-level2-fixes, commit "mld: update statistics after the Reports of the solicited-node groups".

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv6.Ipv6InterfaceData | behavior.change.fix | fingerprint statistical test whatsnew | igmp-mld-model-repairs
Before this commit, two default intervals of IGMP had the values of older documents (gap 3 of the IGMP results). unsolicitedReportInterval defaulted to 10 s, the value of RFC 2236 section 8.10. groupMembershipInterval defaulted to [Robustness Variable] x [Query Interval] + [Query Response Interval], the formula of RFC 3376 section 8.4. A host that kept the default waited up to 10 s to repeat its State-Change Report, and a router ended a group 10 s too early.

After this commit, both defaults of Igmpv3.ned follow RFC 9776. unsolicitedReportInterval is 1 s (RFC 9776 section 8.11). groupMembershipInterval adds twice the Query Response Interval (RFC 9776 section 8.4), which gives 270 s with the other defaults. A host repeats its State-Change Report 1 s after the first one, and a router ends a group 10 s later.

The Older-Host-Present Timer of a router also ran with groupMembershipInterval. Under RFC 3376 the two intervals were equal. RFC 9776 section 8.13 keeps the Older Host Present Interval at [Robustness Variable] x [Query Interval] + [Query Response Interval]. So the timer now has its own parameter, olderHostPresentInterval, with that default, and keeps its value of 260 s. No test measures this value yet (RFC9776-TIMER-25 is owed to a later level 2 pass).

To see the defect, run Rfc9776JoinRepeated and Rfc9776MembershipTimeout of tests/protocol/igmp on the parent commit: both fail at observation 2.

Fingerprints: 1 configuration moves, examples/inet/igmp -c IGMPv3, in all three ingredient sets, because its hosts repeat their Reports sooner.

Statistical results: 1 result moves, examples/inet/igmp -c IGMPv3, whose hosts repeat their Reports sooner. The new results are in the statistics repository, branch topic/standards-tests-igmp-mld-level2-fixes, commit "igmp: update statistics after the default intervals of RFC 9776".

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4.Igmpv3 | behavior.change.fix | fingerprint statistical test whatsnew | igmp-mld-model-repairs
Before this commit, two default intervals of MLD had the values of older documents (gap 6 of the MLD results), the twin of IGMP gap 3. unsolicitedReportInterval defaulted to 10 s, the value of RFC 2710 section 7.10. groupMembershipInterval, the Multicast Address Listening Interval, defaulted to [Robustness Variable] x [Query Interval] + [Query Response Interval], the formula of RFC 3810.

After this commit, both defaults of Mldv2.ned follow RFC 9777. unsolicitedReportInterval is 1 s (RFC 9777 section 9.11). groupMembershipInterval adds a second Query Response Interval (RFC 9777 section 9.4), which gives 270 s with the other defaults.

The Older-Version-Host-Present Timer of a router ran with groupMembershipInterval, which RFC 3810 made equal to it. RFC 9777 section 9.13 keeps that interval at [Robustness Variable] x [Query Interval] + [Query Response Interval]. So the timer now has its own parameter, olderVersionHostPresentInterval, with that default, and keeps its value of 260 s. No test measures this value yet (RFC9777-TIMER-19 is owed to a later level 2 pass).

To see the defect, run Rfc9777StartRepeated and Rfc9777ListeningTimeout of tests/protocol/mld on the parent commit: both fail at observation 2.

Fingerprints: 2 configurations move, in tplx, ~tNl and ~tND: ipv6/mld -c MldV2Ssm and pim ssm_ipv6, whose hosts repeat their Reports sooner.

Statistical results: 2 results move, ipv6/mld -c MldV2Ssm and pim ssm_ipv6, whose hosts repeat their Reports sooner. The new results are in the statistics repository, branch topic/standards-tests-igmp-mld-level2-fixes, commit "mld: update statistics after the default intervals of RFC 9777".

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv2 | behavior.change.fix | fingerprint statistical test whatsnew | igmp-mld-model-repairs
Before this commit, the IGMPv2 mode of a host ended after the Other Querier Present Interval (gap 4 of the IGMP results). Igmpv3::processOlderVersionQuery started the Older-Version-Querier-Present Timer with otherQuerierPresentInterval, 255 s with the defaults, the interval of the querier election of a router. With Queries every 125 s, the difference shows only when the IGMPv2 querier stops: the host went back to IGMPv3 95 s early.

After this commit, the timer runs for [Robustness Variable] x [Query Interval] + 10 x the Max Response Time of the last received Query, 350 s with the defaults (RFC 9776 section 8.12). The Max Response Time comes from the IGMPv2 Query. An IGMPv1 Query has none, and a host reads it as 10 s (RFC 2236 section 4).

To see the defect, run Rfc9776OlderQuerierInterval of tests/protocol/igmp on the parent commit: it fails at observation 2, where A sends a Version 3 Report at 286.25 s.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4.Igmpv3 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the checks with an MLDv1 router ("the link with an older querier" and "the two routers of different versions" of mld/checks.md) counted its General Queries from the start: 0 and 31.25 seconds, then every 125 seconds. Since the repair of MLD gap 2, a querier starts when it has a tested link-local address, the source of every Query (RFC9777-GEN-2). A router of the scenarios has one only after its boot delay and its Duplicate Address Detection, about 1.5 s after the start. Four tests then failed at a Query before their documented observation: Rfc9777ListenerV1Mode and Rfc9777ListenerV1ModeRepeat (a Query within 1 s), Rfc9777OlderQuerierInterval (the Query of 31.25 s) and Rfc9777ModeChangeCancels (the Query of 406.25 s). Rfc9777ListenerBackToV2 still passed, with 0.24 s left of its margin of 1.75 s.

After this commit, the MLDv1 router of these checks is up at the start with its link-local address. It has no boot delay and skips Duplicate Address Detection (DupAddrDetectTransmits 0, which RFC 4862 section 5.1 allows). checks.md says so, and the 9 tests with an MLDv1 router set it. Their Queries come at the times that the checks compute again.

Rfc9777ModeChangeCancels also counted every Version 2 Report of A before the Query, to know whether the repetition of the Report for G left before it. Since MLD gap 5, A reports its solicited-node groups at the start too, so the count was wrong. It now counts the Reports for G.

Each test now fails at its documented observation again: ListenerV1ModeRepeat at 3, OlderQuerierInterval at 2, ModeChangeCancels at 2, Rfc2710ReportSuppression at 2, and QuerierConfiguredV1, declared expected, at 2. Rfc9777ListenerV1Mode fails at observation 5 and not at 4. A sends Version 2 Reports for its solicited-node groups at 1.83 s, in MLDv1 mode. They wait for its link-local address, and the MLDv1 mode does not cancel the MLDv2 retransmissions, the defect of MLD gap 10. The other tests with an MLDv1 router pass, as before.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: tests | behavior.change.fix | - | igmp-mld-model-repairs
Before this commit, the MLDv1 mode of a node ended after the Other Querier Present Interval (gap 7 of the MLD results), the twin of IGMP gap 4. Mldv2::processOlderVersionQuery started the Older Version Querier Present Timer with otherQuerierPresentInterval, 255 s with the defaults, the interval of the querier election of a router. With Queries every 125 s, the difference shows only when the MLDv1 querier stops: the node went back to MLDv2 5 s early.

After this commit, the timer runs for [Robustness Variable] x [Query Interval] of the last Query + [Query Response Interval], 260 s with the defaults (RFC 9777 section 9.12). An MLDv1 Query carries no Query Interval, so the own Query Interval of the node stands for it.

To see the defect, run Rfc9777OlderQuerierInterval of tests/protocol/mld on the parent commit: it fails at observation 2, where A sends a Version 2 Report at 290 s.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv2 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the plan did not record that IGMP gaps 1 to 4 and MLD gaps 1 to 7 have a repair.

After this commit, the plan records the repair of IGMP gaps 1 to 4 and MLD gaps 1 to 7.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: plan | behavior.change | - | igmp-mld-model-repairs
Before this commit, a second change replaced the pending records instead of a merge (gap 5 of the IGMP results). Igmpv3::multicastSourceListChanged computed the records of a change from the old and the new state, and replaced the pending records with them, as a "merge by recomputation".

After this commit, a host group keeps retransmission state: filterModeChangeCount, and a count for each source in sourceChangeCounts. RFC 9776 section 5.1 keeps this state for each source of a change until [Robustness Variable] State-Change Reports have held it. A filter-mode change puts a Filter-Mode-Change record into the next [Robustness Variable] Reports. Table 4 builds each Report from that state.

A change sets the counts of the sources of its difference (Table 3) and, for a filter-mode change, the group count. It sends the merged Report at once. Every State-Change Report that leaves decrements the counts, and the Reports repeat while a count is left. A Report holds TO_IN or TO_EX with the current sources while the group count is positive. Otherwise it holds ALLOW and BLOCK with the sources that still have a count, by the current state.

To see the defect, run Rfc9776ChangeDuringRepetitions of tests/protocol/igmp on the parent commit: it fails at observation 2, where the Report at 10.3 s holds ALLOW_NEW_SOURCES {S2} without S1.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4.Igmpv3 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, a second change replaced the pending records instead of a merge (gap 8 of the MLD results), the twin of IGMP gap 5. Mldv2::multicastSourceListChanged replaced the pending records with the records of the latest change.

After this commit, a host group keeps retransmission state, as in Igmpv3: filterModeChangeCount, and a count for each source in sourceChangeCounts. RFC 9777 section 6.1 keeps this state for each source of a change until [Robustness Variable] State-Change Reports have held it. A filter-mode change puts a Filter-Mode-Change record into the next [Robustness Variable] Reports. Table 2 builds each Report from that state.

A change sets the counts of the sources of its difference (Table 1) and, for a filter-mode change, the group count. It sends the merged Report at once. Every State-Change Report that leaves decrements the counts, and the Reports repeat while a count is left.

To see the defect, run Rfc9777ChangeDuringRepetitions of tests/protocol/mld on the parent commit: it fails at observation 2, where the Report at 10.3 s holds ALLOW_NEW_SOURCES {S2} without S1.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv2 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the IGMPv2 mode of a host answered at once and sent one Report (gap 6 of the IGMP results). Igmpv3::processOlderVersionQuery sent the IGMPv2 Reports in the event of the Query. Igmpv3::multicastSourceListChanged sent one IGMPv2 Report for a join and returned. The mode had no report delay timer, so it had no Delaying Member state and no suppression.

After this commit, a host group has the report delay timer and the flag of RFC 2236 section 6 for the IGMPv1 and IGMPv2 modes. RFC 9776 section 7.2.1 makes a host in that mode act as an IGMPv2 host, and RFC 2236 section 6 gives the host state machine.

A Query starts the timer with a random value up to its Max Response Time (10 s for an IGMPv1 Query). It resets the timer when the Max Response Time is less than the time left. The expiry sends the Report and sets the flag. The IGMPv1 or IGMPv2 Report of another member stops the timer and clears the flag. A join sends the Report, sets the flag and starts the timer with a value up to unsolicitedReportInterval, which repeats the Report. A leave stops the timer, and sends the Leave only when the flag is set. The return to IGMPv3 stops the timers.

To see the defect, run Rfc9776HostV2Mode, Rfc9776HostV2ModeRepeat and Rfc2236ReportSuppression of tests/protocol/igmp on the parent commit: they fail at observations 4, 3 and 2.

The module test IGMPv3_interop_host sent its IGMPv1 Query 4 s after its IGMPv2 Query, whose Max Response Time was 10 s. The answer to the IGMPv2 Query came after the IGMPv1 Query and was an IGMPv1 Report. The IGMPv2 Query now has a Max Response Time of 1 s.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4.Igmpv3 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the program of Rfc2710ReportSuppression counted every MLDv1 Report of A and B. Observation 2 of the check "Report suppression in MLDv1 mode" (doc/project/evidence/protocol/mld/checks/compatibility.md) is that no second Report for G leaves A or B within the Maximum Response Delay of the Query. Observation 1 is a Report for G from A or B. While A and B listened only to G, the two were the same. Since the repair of MLD gap 5, A and B also report their solicited-node groups, and in MLDv1 mode they answer the Query for them too. With the repair of MLD gap 9, which delays and suppresses the answers, the answers for the solicited-node groups made the count exceed 1 at 161.79 s, although only one Report for G left.

After this commit, the counters and the first step take only the Reports for G, as the check says.

At this commit, the test still fails at observation 2, at 156.25 s, where A and B both answer for G at the instant of the Query. This is the defect of MLD gap 9.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: tests | behavior.change.fix | - | igmp-mld-model-repairs
Before this commit, a node in MLDv1 mode answered a Query at once and sent one Report for a start of listening (gap 9 of the MLD results, the twin of IGMP gap 6). Mldv2::processOlderVersionQuery sent the MLDv1 Reports in the event of the Query. Mldv2::multicastSourceListChanged sent one MLDv1 Report for a start of listening and returned. RFC 9777 section 8.2.1 makes a node in MLDv1 mode act as an MLDv1 node, and RFC 2710 section 5 gives the node state machine.

After this commit, a host group has the report delay timer and the flag of RFC 2710 section 5 for the MLDv1 mode, as in Igmpv3. A Query starts the timer with a random value up to its Maximum Response Delay, or resets it when that value is less than the time left. The expiry sends the Report and sets the flag. The MLDv1 Report of another node stops the timer and clears the flag.

A start of listening sends the Report, sets the flag and starts the timer with a value up to unsolicitedReportInterval, which repeats the Report. A stop of listening stops the timer, and sends the Done only when the flag is set. The return to MLDv2 stops the timers.

To see the defect, run Rfc9777ListenerV1ModeRepeat and Rfc2710ReportSuppression of tests/protocol/mld on the parent commit: Rfc9777ListenerV1ModeRepeat fails at observation 3, and Rfc2710ReportSuppression fails at observation 2.

Rfc9777ListenerV1Mode passes observation 4 now, and still fails at observation 5, which the commit "tests: fix: the MLDv1 router of the MLD checks is up at the start" explained: the MLDv1 mode keeps the MLDv2 transmissions of the solicited-node groups, MLD gap 10. Its defect paragraph says so now.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv2 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the IGMPv2 mode kept the pending IGMPv3 retransmissions (gap 7 of the IGMP results). Igmpv3::processOlderVersionQuery set the compatibility mode and started its timer, and cancelled no timer. So an IGMPv3 host that entered the IGMPv2 mode still sent the repetitions of its IGMPv3 State-Change Report.

After this commit, a change of the Host Compatibility Mode cancels all pending response and retransmission timers. RFC 9776 section 7.2.1 requires this.

The new Igmpv3::cancelHostTimers cancels the response timer of the General Queries, the response and retransmission timers of each group with their state, and the report delay timers of the older modes. A General Query that changes the mode calls it, and so does the return to IGMPv3.

To see the defect, run Rfc9776ModeChangeCancels of tests/protocol/igmp on the parent commit: it fails at observation 2, where A sent a Version 3 Report at 406.53 s, after the IGMPv2 Query of 406.25 s.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4.Igmpv3 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the MLDv1 mode kept the pending MLDv2 retransmissions (gap 10 of the MLD results, the twin of IGMP gap 7). Mldv2::processOlderVersionQuery set the compatibility mode and started its timer, and cancelled no timer.

After this commit, a change of the Host Compatibility Mode cancels all pending responses and retransmission timers. RFC 9777 section 8.2.1 requires this.

The new Mldv2::cancelHostTimers cancels the response timer of the General Queries, the response and retransmission timers of each group with their state, and the report delay timers of the MLDv1 mode. It also deletes the Reports of the old mode that wait for the link-local address of the interface (the repair of MLD gap 2). They are pending too, and would leave in the new mode. A General Query that changes the mode calls it, and so does the return to MLDv2.

To see the defect, run Rfc9777ModeChangeCancels and Rfc9777ListenerV1Mode of tests/protocol/mld on the parent commit. Rfc9777ModeChangeCancels fails at observation 2, where A sent a Version 2 Report at 407.05 s, after the MLDv1 Query of 406.25 s. Rfc9777ListenerV1Mode fails at observation 5, where A sent the waiting and repeated Version 2 Reports of its solicited-node groups in MLDv1 mode at 2.52 s.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv2 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, an answer to a Query held a record that the standard leaves out (gap 8 of the IGMP results). Igmpv3::processHostGeneralQueryTimer made one record for each entry of the group table of the interface, and a leave sets the entry to INCLUDE {} and keeps it. Igmpv3::processHostGroupQueryTimer built the record for a Group-and-Source-Specific Query and sent it without a look at its size.

After this commit, the answer to a General Query leaves out the groups in INCLUDE mode with no sources, and holds as many records as it has. The answer to a Group-Specific Query leaves when the group has reception state, and the answer to a Group-and-Source-Specific Query when its record has sources. RFC 9776 section 5.2 sends one Current-State Record for each address with reception state, answers a Group-Specific Query only when the interface has reception state for the group, and sends no record with an empty set of sources.

To see the defect, run Rfc9776GeneralQueryNoState and Rfc9776GroupSourceResponse of tests/protocol/igmp on the parent commit: both fail at observation 5. The first sees a MODE_IS_INCLUDE record for K with no sources, after A left K. The second sees a MODE_IS_INCLUDE record for G with no sources.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4.Igmpv3 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, an answer to a Query held a record that the standard leaves out (gap 11 of the MLD results, the twin of IGMP gap 8). Mldv2::processHostGeneralQueryTimer made a record for every entry of the address table, and a stop of listening keeps the entry. Mldv2::processHostGroupQueryTimer sent the record for a Multicast Address and Source Specific Query without a look at its size.

After this commit, the answers follow the rules of RFC 9777 section 6.3, as in Igmpv3. The answer to a General Query leaves out the addresses in INCLUDE mode with no sources, and holds as many records as it has. The section sends one Current State Record for each address with listening state, answers a Multicast Address Specific Query only when the interface has listening state for the address, and sends no record with an empty set of sources.

To see the defect, run Rfc9777GeneralQueryNoState and Rfc9777AddressSourceResponse of tests/protocol/mld on the parent commit: both fail at observation 5. The first sees a MODE_IS_INCLUDE record for K, which A stopped. The second sees a MODE_IS_INCLUDE record with no sources.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv2 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the S flag of a Group-Specific Query came from the timer before it was lowered (gap 9 of the IGMP results). Igmpv3::sendGroupSpecificQuery computed the flag from the Group Timer and then lowered the timer to the Last Member Query Time. Igmpv3::processRexmtTimer set the flag on every retransmission of a Group-Specific Query without a look at the timer.

After this commit, both functions compute the flag from the Group Timer at the time of the transmission, after it is lowered. The flag is clear unless a Report raised the timer since the first Query. RFC 9776 section 6.6.3.1 lowers the timer first, and sets the "Suppress Router-Side Processing" bit of each transmission when the Group Timer is larger than the Last Member Query Time.

To see the defect, run Rfc9776LastMemberQuerySFlag of tests/protocol/igmp on the parent commit: it fails at observation 6, where the first Group-Specific Query after the leave of the only member has the S flag set.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4.Igmpv3 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the S flag of a Multicast Address Specific Query came from the timer before it was lowered (gap 12 of the MLD results, the twin of IGMP gap 9). Mldv2::sendGroupSpecificQuery computed the flag from the Filter Timer and then lowered the timer. Every retransmission set the flag without a look at the timer.

After this commit, both places compute the flag from the Filter Timer at the time of the transmission, after it is lowered, as in Igmpv3. RFC 9777 section 7.6.3.1 lowers the Filter Timer to the Last Listener Query Time first, and sets the "Suppress Router-Side Processing" bit of each transmission when the Filter Timer is larger than that time.

To see the defect, run Rfc9777LastListenerQuerySFlag of tests/protocol/mld on the parent commit: it fails at observation 6, at the first Query.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv2 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
levy added 15 commits October 27, 2026 10:18
Before this commit, three loops of the IGMPv3 and MLDv2 routers deleted source records while they iterated over the source map of a group. Two loops called deleteSourceRecord(), which erases the entry, and then incremented the iterator of the erased entry. They are the loop of processRouterGroupTimer, which deletes the sources without a timer when the group timer ends in EXCLUDE mode, and the loop of the IS_EX record, which deletes the sources that the record does not name. That is undefined behavior: it can read freed memory and stop the process. Both modules had both loops.

The third loop, the loop of the TO_EX record in EXCLUDE mode of Igmpv3, stepped past the entry first, as it must, but then tested the next entry, or end() at the last one, instead of the current one. So it deleted a source when the source after it was not named in the record. In EXCLUDE(X={4}, Y={3}), TO_EX(1,3) deleted 3, which RFC 9776 section 6.4.2 keeps in Y, and the next step gave it a new timer as a new source. The Mldv2 loop tests the right entry.

After this commit, all three loops step past the entry and then test and delete the entry itself.

No run of the test suites reached the first two paths before this commit. The next changes of this branch (IGMP gaps 10 and 11) lower the Source Timers of a Group-and-Source-Specific Query, so a source record whose timer has ended stays in EXCLUDE mode. The module tests IGMPv3_router2, IGMPv3_router3 and IGMPv3_router4 then stopped with a segmentation fault in the loop of the group timer, and IGMPv3_router2 showed the wrong deletion.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer | behavior.change.fix | - | igmp-mld-model-repairs
Before this commit, a router in EXCLUDE(X,Y) that got BLOCK(A) did not add the new sources. Igmpv3 and Mldv2 meant to add them in a loop, but the loop iterated over the source map of the group and asked whether the map held each of its own entries, so it never added a source. The Query then named sources that the router had no record of.

After this commit, the loop iterates over the sources of the record. RFC 9776 section 6.4.2 (RFC9776-RREP-25), and RFC 9777 section 7.4.2 for MLD, move the router to EXCLUDE(X+(A-Y),Y), set the timers of the new sources A-X-Y to the Group Timer, and send Q(G,A-Y).

The forwarding does not change: in EXCLUDE mode a source outside Y is forwarded with or without a record. The router now has records for these sources, which the Query that follows needs. The next change of this branch (IGMP gaps 10 and 11) lowers the Source Timer of each queried source that has a record.

No test measures this row yet: RFC9776-RREP-25 is owed to a later level 2 pass.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer | behavior.change.fix | - | igmp-mld-model-repairs
Before this commit, the Group-Specific and Group-and-Source-Specific Queries had two defects, both in RFC 9776 section 6.6.3. In gap 10, a Report cancelled the retransmissions of the Queries. Igmpv3::processReport cancelled them when any record for the group arrived. The retransmission queried only the sources whose timer was above the Last Member Query Time (LMQT), and never set the S flag of a Group-and-Source-Specific Query. In gap 11, a Group-and-Source-Specific Query did not lower the Source Timers. So a source that the last member of an INCLUDE group blocked stayed forwarded until its Source Timer ended.

After this commit, the retransmission state of the group and of each source stays, and an answer changes only the timers (section 6.6.3). At each transmission, the sources with a timer larger than LMQT go in a Query with the S flag, and the others go in a second Query without it (section 6.6.3.2). That section also lowers the timer of each queried source that is larger than LMQT to LMQT, and gives that source [Last Member Query Count] transmissions.

The two gaps are halves of one mechanism, so they are repaired together. Without the lowering, the new retransmission state would query every source with the S flag. With the lowering alone, the old retransmission would query no source.

A router group now keeps groupRexmtCount for the Group-Specific Query and sourceRexmtCounts for the sources, and processReport no longer cancels them. sendGroupAndSourceSpecificQuery lowers the queried sources and sets their counts, and the new sendSourceSpecificQueries sends the two Queries. processRexmtTimer sends both kinds while counts are left.

To see the defect, run Rfc9776SourceBlockedOtherMember and Rfc9776SourceBlockedOnlyMember of tests/protocol/igmp on the parent commit. The first fails at observation 3, where no second Query follows the answer of A. The second fails at observation 2, where a datagram of S1 arrives at 52.5 s. Rfc9776SourceQuerySFlag now sees the second Query and its S flag too.

Module tests IGMPv3_router2 and IGMPv3_router3 change their expectations. IGMPv3_router2 expected Q(G,X) to name the sources that are already in their query period, and a Q(G) to replace the retransmissions of a Q(G,X-A). IGMPv3_router3 expected the queried sources to live on. Both now expect the sequence of RFC 9776 sections 6.4.2, 6.5 and 6.6.3, and their descriptions say which timers end. The two commits before this one repaired the loops that these runs reached first.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4.Igmpv3 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the Multicast Address Specific and Multicast Address and Source Specific Queries had two defects, the twins of IGMP gaps 10 and 11, both in RFC 9777 section 7.6.3. In gap 13, a Report cancelled the retransmissions of the Queries. Mldv2::processReport cancelled them when any record for the address arrived. The retransmission queried only the sources whose timer was above the Last Listener Query Time (LLQT), without the S flag. In gap 14, a Multicast Address and Source Specific Query did not lower the Source Timers.

After this commit, the retransmission state of the address and of each source stays, and an answer changes only the timers (section 7.6.3). At each transmission, the sources with a timer larger than LLQT go in a Query with the S flag, and the others go in a second Query without it (section 7.6.3.2). That section also lowers the timer of each queried source that is larger than LLQT to LLQT, and gives that source [Last Listener Query Count] transmissions.

As in Igmpv3, the two gaps are repaired together. A router group keeps groupRexmtCount and sourceRexmtCounts, and processReport no longer cancels them. sendGroupAndSourceSpecificQuery lowers the queried sources, and sendSourceSpecificQueries sends the two Queries.

To see the defect, run Rfc9777SourceBlockedOtherListener and Rfc9777SourceBlockedOnlyListener of tests/protocol/mld on the parent commit: the first fails at observation 3, and the second fails at observation 2.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv2 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the forwarding asked for a listener of the group, not of the source (gap 12 of the IGMP results). IPv4 forwarded a multicast datagram onto a leaf interface when Ipv4InterfaceData::hasMulticastListener(group) held, in Ipv4::datagramLocalOut and in Ipv4::forwardMulticastPacket. Igmpv3::processReport stores the forwarded sources of each group with setMulticastListeners, and hasMulticastListener(group, source) exists, but no caller asked it.

After this commit, both places ask hasMulticastListener(group, source) with the source address of the datagram. RFC 9776 section 6.3 forwards a source onto the link only when the IGMPv3 state wants it.

That function returned the negation of sourceList.contains(source), which is true when the source is wanted. It now returns contains(source), as its Ipv6InterfaceData twin does. A listener that joins for all sources, as Igmpv2 and the configurators add them, has the source list EXCLUDE {}, which contains every source, so its forwarding does not change.

To see the defect, run Rfc9776SourceSpecificBlocking of tests/protocol/igmp on the parent commit: it fails at observation 3, where the first datagram of S2 arrives on L1 at 10.00005 s although A wants S1 only.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the forwarding asked for a listener of the address, not of the source (gap 15 of the MLD results, the twin of IGMP gap 12). Ipv6::routeMulticastPacket forwarded a multicast datagram onto a leaf interface when Ipv6InterfaceData::hasMulticastListener(address) held. Mldv2::processReport stores the forwarded sources with setMulticastListeners, and hasMulticastListener(address, source) exists, but the forwarding never asked it.

After this commit, the forwarding asks hasMulticastListener(address, source) with the source address of the datagram. RFC 9777 section 7.3 forwards a source onto the link only when the MLDv2 state wants it. A listener for all sources has the source list EXCLUDE {}, so its forwarding does not change.

To see the defect, run Rfc9777SourceSpecificBlocking of tests/protocol/mld on the parent commit: it fails at observation 3, where the first datagram of S2 arrives on L1 at 10.00006 s although A wants S1 only.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv6.Ipv6 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the step of the program in Rfc9777RouterV1Listener took any MLDv1 Report of C. Observation 1 of the check "Router with an MLDv1 listener" (doc/project/evidence/protocol/mld/checks/compatibility.md) is the MLDv1 Report of C for G, and observation 2 is a datagram of S1 on L1 within 1 s of it. Since the repair of MLD gap 5, C reports its solicited-node group too, at about 2.6 s. With the repair of MLD gap 16, which lets the MLDv1 node C read the MLDv2 Query of R, the run reaches this step, and the step took that Report. The datagram of S1 was then due by 3.6 s, long before C asks for G at 10 s.

After this commit, the step takes only a Report for G, as the check and the description of the step say.

At this commit the test still stops at the start on the MLDv2 Query of R, the defect of MLD gap 16.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: tests | behavior.change.fix | - | igmp-mld-model-repairs
Before this commit, an MLDv1 node stopped the simulation on an MLDv2 Query (gap 16 of the MLD results). Mldv1::processQuery read every Query as an MldQuery of 24 octets, and the 28 octets of an MLDv2 Query cannot be read so: "Cannot convert chunk from type inet::Mldv2Query to type inet::MldQuery". So the MLDv1 interoperation of the model could not run with its own MLDv1 module.

After this commit, the function reads the Query as an MldMessage, the base of both Query classes. Its first 24 octets are the fields of an MLDv1 Query, and the Maximum Response Code of an MLDv2 Query stands for the Maximum Response Delay. RFC 2710 section 5 (RFC2710-NODE-2) and RFC 9777 section 8.1 make a node accept every Query of 24 octets or more. RFC 9777 section 8.3.2 keeps an MLDv2 querier on MLDv2 Queries on a link with an MLDv1 listener.

To see the defect, run Rfc9777RouterV1Listener and Rfc9777BlockInV1Mode of tests/protocol/mld on the parent commit: both stop at the start. After the repair, the router statements of the checks that the level 2 pass could not reach get their verdicts: RFC9777-COMPR-12 to COMPR-16, COMPR-19 to COMPR-23 and TIMER-18.

Rfc9777QuerierConfiguredV1, declared expected, no longer stops in R2, and fails at observation 2, the missing MLDv1 mode of R1 (MLD gap 17). Its expected-result paragraph says so now.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv1 | behavior.change.fix | test whatsnew | igmp-mld-model-repairs
Before this commit, the level 2 pass put RFC9776-COMPR-3 and COMPH-18 to a later level. It read them as the behavior of a version 1 system, which the model does not have. RFC 9776 section 7.3.1 also gives an IGMPv3 router a configuration option to act in IGMPv1 mode (COMPR-2), and the repair of IGMP gap 13 that follows adds it. With it, the IGMPv1 Queries and the IGMPv1 mode of a host are observable on the ordinary link.

After this commit, the new check "Querier configured in IGMPv1 mode" in checks/compatibility.md observes the General Queries of R, configured in IGMPv1 mode, and the Report of A for a join. It expects 8 octets with a Max Resp Code of 0, and an IGMPv1 Report with no IGMPv2 or IGMPv3 message. checks.md lists the check.

COMPR-4, the Leave that a router in IGMPv1 mode ignores, stays at a later level. No host in IGMPv1 mode sends a Leave.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: doc | behavior.add | - | igmp-mld-model-repairs
Before this commit, an IGMPv3 router had no IGMPv2 querier mode (gap 13 of the IGMP results, a missing feature). Igmpv3 built only Igmpv3Query messages and had no parameter for a compatibility mode. An IGMPv2 General Query reached the router half of the module only for the querier election. RFC 9776 section 6.6.2 makes a router that receives an older General Query use the oldest version of the network (RFC9776-RQRY-8), and section 7.3.1 gives it a configuration option to act in IGMPv1 or IGMPv2 mode (COMPR-2).

After this commit, the new parameter routerVersion (default 3) sets the version that the router acts in, and each router interface keeps its own version. In IGMPv2 mode the router sends General and Group-Specific Queries of 8 octets with the Max Response Time in tenths of a second in the Max Resp Code, without the exponential code (COMPR-6, COMPR-8). In IGMPv1 mode its General Queries have a Max Resp Code of 0 (COMPR-3), it sends no Group-Specific Query, and it ignores Leave Group messages (COMPR-4).

Neither mode sends a Group-and-Source-Specific Query, which the older versions do not have. An IGMPv1 or IGMPv2 General Query lowers the version of its interface. A Query of another version than the configured one gets a warning, once for each interface (COMPR-5, COMPR-7, COMPR-9, COMPR-10).

Rfc9776QuerierV2Router of tests/protocol/igmp checks the IGMPv2 mode, and no longer declares an expected failure. The new Rfc9776QuerierConfiguredV1 (the check "Querier configured in IGMPv1 mode" of the commit before) checks the IGMPv1 mode. It fails at the first General Query when the router keeps version 3.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.ipv4.Igmpv3 | behavior.add | test whatsnew | igmp-mld-model-repairs
Before this commit, an MLDv2 router had no MLDv1 mode (gap 17 of the MLD results, a missing feature). Mldv2 built only MLDv2 Queries and had no configuration option. RFC 9777 section 8.3.1 makes the querier use the lowest version of MLD on the link, as the administrator assures it: an MLDv2 router that wants to be compatible with MLDv1 has a configuration option to act in MLDv1 mode (RFC9777-COMPR-1, COMPR-2).

After this commit, the new parameter routerVersion (default 2) sets the version that the router acts in. In MLDv1 mode the router sends General and Multicast Address Specific Queries of 24 octets, with the Maximum Response Delay in milliseconds in the Maximum Response Code, without the exponential code (COMPR-3, COMPR-4, COMPR-7). It sends no Multicast Address and Source Specific Query, which MLDv1 does not have.

An MLDv2 Query in MLDv1 mode, and an MLDv1 General Query at a router that is not configured for MLDv1, get a warning, once for each interface (COMPR-5 and the second rule of section 8.3.1). Unlike IGMP, RFC 9777 has no rule that changes the mode on an older Query, so the mode comes only from the parameter.

Rfc9777QuerierConfiguredV1 of tests/protocol/mld checks the MLDv1 mode with R1 configured with routerVersion = 1, as its check says, and no longer declares an expected failure.

No fingerprint moves.

No statistical result moves.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: src.networklayer.icmpv6.Mldv2 | behavior.add | test whatsnew | igmp-mld-model-repairs
Before this commit, the plan did not record the repair of IGMP gaps 5 to 13 and MLD gaps 8 to 17.

After this commit, the plan records that these gaps are repaired.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: plan | behavior.change | - | igmp-mld-model-repairs
…airs

Before this commit, the results, ledgers and matrices of IGMP and MLD described the state before the repairs.

After this commit, they follow a fresh run: IGMP 43 tests, 43 PASS; MLD 47 tests, 47 PASS. results.md of each protocol marks every gap repaired with the commit of its repair, and keeps the description of the old code. Other findings adds the defects that the repairs found on the way: the source-map loops and the BLOCK loop of both routers, the inverted hasMulticastListener of IPv4, the MLDv1 stop on an MLDv2 Report, and the undeclared packetDropped signal of Icmpv6, which is not repaired.

The IGMP ledger has 153 selected, 111 covered, 49 owed, 71 later and 3 statements without a check; 7 features are supported, 11 partial and 2 untested. The MLD ledger keeps its 58 owed statements; 8 features are supported, 11 partial and 1 untested. In part 2 of conformance.md no core check fails: IGMP has 7 features confirmed, 11 partial, 1 unverified and 1 out of claim; MLD has 8 confirmed, 11 partial and 1 out of claim, and no defect.

notes.md of each protocol gets a "Fixed on" entry for each repaired quirk, the new quirks of the repairs (the IGMPv2 and IGMPv1 modes of a router, the link-local source that waits for Duplicate Address Detection, the solicited-node Reports), the lesson that a verdict can hide a new failure reason, and new follow-ups.

The closing list of the IGMP checks moves the four statements that the new check "Querier configured in IGMPv1 mode" reads out of "A version 1 system". It moves the Leave at a router in IGMPv1 mode to the crafted messages, and its warning to the state inside a node. The ledger scripts are copies of the level 2 ones in audit/igmp-mld-level2-fixes/ of inet-master, outside git.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: doc | behavior.change | - | igmp-mld-model-repairs
Before this commit, the repair plan showed its last steps as open: the documents, the statistics and the gates.

After this commit, the plan marks every step done. It records that the documents follow a fresh run, and that the statistics branch has a commit for each of the five commits that move results.

Plan: plan/pending/igmp-mld-level2-fixes.md
Change: plan | behavior.change | - | igmp-mld-model-repairs
Change: plan | location | - | igmp-mld-model-repairs

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant