From 89062d59851eaeb9f0c2acf3c265cc6d26aea463 Mon Sep 17 00:00:00 2001 From: Ghanan Gowripalan Date: Fri, 13 Jan 2023 12:17:52 -0800 Subject: [PATCH] Support unordered MGP record validation This change updates the checker and network testutil packages to support the validation of unordered MGP(v2) records which may be present across one or more reports. This allows us to share more code across the IP-generic and IP-specific multicast group protocol tests while also preparing us for followup changes which coalesces records in unpredictable order. Updates #8346 PiperOrigin-RevId: 501910064 --- pkg/tcpip/checker/checker.go | 137 +++-- pkg/tcpip/network/internal/testutil/BUILD | 2 + .../network/internal/testutil/testutil.go | 201 ++++++- pkg/tcpip/network/ipv6/mld_test.go | 194 +------ pkg/tcpip/network/multicast_group_test.go | 524 ++++-------------- 5 files changed, 401 insertions(+), 657 deletions(-) diff --git a/pkg/tcpip/checker/checker.go b/pkg/tcpip/checker/checker.go index 93c3df24c..49ba2d916 100644 --- a/pkg/tcpip/checker/checker.go +++ b/pkg/tcpip/checker/checker.go @@ -1223,27 +1223,47 @@ func MLDMaxRespDelay(want time.Duration) TransportChecker { } } -// MLDMulticastAddress creates a checker that checks the Multicast Address -// field of a MLD message. +// MLDMulticastAddressUnordered creates a checker that checks that the multicast +// address in the MLD message is expected to be seen. +// +// The seen address is removed from the expected groups map. // // The returned TransportChecker assumes that a valid ICMPv6 is passed to it // containing a valid MLD message as far as the size is concerned. -func MLDMulticastAddress(want tcpip.Address) TransportChecker { +func MLDMulticastAddressUnordered(expectedGroups map[tcpip.Address]struct{}) TransportChecker { return func(t *testing.T, h header.Transport) { t.Helper() icmp := h.(header.ICMPv6) ns := header.MLD(icmp.MessageBody()) - if got := ns.MulticastAddress(); got != want { - t.Errorf("got %T.MulticastAddress() = %s, want = %s", ns, got, want) + addr := ns.MulticastAddress() + + if _, ok := expectedGroups[addr]; !ok { + t.Errorf("unexpected multicast group %s", addr) + } else { + delete(expectedGroups, addr) } } } +// MLDMulticastAddress creates a checker that checks the Multicast Address +// field of a MLD message. +// +// The returned TransportChecker assumes that a valid ICMPv6 is passed to it +// containing a valid MLD message as far as the size is concerned. +func MLDMulticastAddress(want tcpip.Address) TransportChecker { + return MLDMulticastAddressUnordered(map[tcpip.Address]struct{}{ + want: struct{}{}, + }) +} + // MLDv2Report creates a checker that checks that the packet contains a valid // MLDv2 report with the specified records. -func MLDv2Report(expectedReport header.MLDv2ReportSerializer) NetworkChecker { +// +// Note that observed records are removed from expectedRecords. No error is +// logged if the report does not have all the records expected. +func MLDv2Report(expectedRecords map[tcpip.Address]header.MLDv2ReportRecordType) NetworkChecker { return func(t *testing.T, h []header.Network) { t.Helper() @@ -1255,16 +1275,26 @@ func MLDv2Report(expectedReport header.MLDv2ReportSerializer) NetworkChecker { last := h[len(h)-1] icmp := header.ICMPv6(last.Payload()) report := header.MLDv2Report(icmp.MessageBody()) - expectedRecords := expectedReport.Records records := report.MulticastAddressRecords() for len(expectedRecords) != 0 { record, res := records.Next() - if res != header.MLDv2ReportMulticastAddressRecordIteratorNextOk { - t.Fatalf("got records.Next() = (%#v, %d), want = (_, %d)", record, res, header.MLDv2ReportMulticastAddressRecordIteratorNextOk) + switch res { + case header.MLDv2ReportMulticastAddressRecordIteratorNextOk: + case header.MLDv2ReportMulticastAddressRecordIteratorNextDone: + return + default: + t.Fatalf("unhandled res = %d", res) } - if got, want := record.RecordType(), expectedRecords[0].RecordType; got != want { + addr := record.MulticastAddress() + expectedRecordType, ok := expectedRecords[addr] + if !ok { + t.Errorf("unexpected record for address %s", addr) + continue + } + + if got, want := record.RecordType(), expectedRecordType; got != want { t.Errorf("got record.RecordType() = %d, want = %d", got, want) } @@ -1272,30 +1302,17 @@ func MLDv2Report(expectedReport header.MLDv2ReportSerializer) NetworkChecker { t.Errorf("got record.AuxDataLen() = %d, want = 0", got) } - if got, want := record.MulticastAddress(), expectedRecords[0].MulticastAddress; got != want { - t.Errorf("got record.MulticastAddress() = %s, want = %s", got, want) - } - sources, ok := record.Sources() if !ok { t.Error("got record.Sources() = (_, false), want = (_, true)") continue } - expectedSources := expectedRecords[0].Sources - for len(expectedSources) != 0 { - source, ok := sources.Next() - if !ok { - t.Fatal("got sources.Next() = (_, false), want = (_, true)") - } - if source != expectedSources[0] { - t.Errorf("got sources.Next() = %s, want = %s", source, expectedSources[0]) - } - - expectedSources = expectedSources[1:] + if source, ok := sources.Next(); ok { + t.Fatalf("got sources.Next() = (%s, true), want = (_, false)", source) } - expectedRecords = expectedRecords[1:] + delete(expectedRecords, addr) } if record, res := records.Next(); res != header.MLDv2ReportMulticastAddressRecordIteratorNextDone { @@ -1580,8 +1597,14 @@ func IGMPMaxRespTime(want time.Duration) TransportChecker { } } -// IGMPGroupAddress creates a checker that checks the IGMP Group Address field. -func IGMPGroupAddress(want tcpip.Address) TransportChecker { +// IGMPGroupAddressUnordered creates a checker that checks that the group +// address in the IGMP message is expected to be seen. +// +// The seen address is removed from the expected groups map. +// +// The returned TransportChecker assumes that a valid IGMP is passed to it +// containing a valid IGMP message as far as the size is concerned. +func IGMPGroupAddressUnordered(expectedGroups map[tcpip.Address]struct{}) TransportChecker { return func(t *testing.T, h header.Transport) { t.Helper() @@ -1589,15 +1612,30 @@ func IGMPGroupAddress(want tcpip.Address) TransportChecker { if !ok { t.Fatalf("got transport header = %T, want = header.IGMP", h) } - if got := igmp.GroupAddress(); got != want { - t.Errorf("got igmp.GroupAddress() = %s, want = %s", got, want) + + addr := igmp.GroupAddress() + + if _, ok := expectedGroups[addr]; !ok { + t.Errorf("unexpected multicast group %s", addr) + } else { + delete(expectedGroups, addr) } } } +// IGMPGroupAddress creates a checker that checks the IGMP Group Address field. +func IGMPGroupAddress(want tcpip.Address) TransportChecker { + return IGMPGroupAddressUnordered(map[tcpip.Address]struct{}{ + want: struct{}{}, + }) +} + // IGMPv3Report creates a checker that checks that the packet contains a valid // IGMPv3 report with the specified records. -func IGMPv3Report(expectedReport header.IGMPv3ReportSerializer) NetworkChecker { +// +// Note that observed records are removed from expectedRecords. No error is +// logged if the report does not have all the records expected. +func IGMPv3Report(expectedRecords map[tcpip.Address]header.IGMPv3ReportRecordType) NetworkChecker { return func(t *testing.T, h []header.Network) { t.Helper() @@ -1612,16 +1650,26 @@ func IGMPv3Report(expectedReport header.IGMPv3ReportSerializer) NetworkChecker { } report := header.IGMPv3Report(igmp) - expectedRecords := expectedReport.Records records := report.GroupAddressRecords() for len(expectedRecords) != 0 { record, res := records.Next() - if res != header.IGMPv3ReportGroupAddressRecordIteratorNextOk { - t.Fatalf("got records.Next() = (%#v, %d), want = (_, %d)", record, res, header.IGMPv3ReportGroupAddressRecordIteratorNextOk) + switch res { + case header.IGMPv3ReportGroupAddressRecordIteratorNextOk: + case header.IGMPv3ReportGroupAddressRecordIteratorNextDone: + return + default: + t.Fatalf("unhandled res = %d", res) } - if got, want := record.RecordType(), expectedRecords[0].RecordType; got != want { + addr := record.GroupAddress() + expectedRecordType, ok := expectedRecords[addr] + if !ok { + t.Errorf("unexpected record for address %s", addr) + continue + } + + if got, want := record.RecordType(), expectedRecordType; got != want { t.Errorf("got record.RecordType() = %d, want = %d", got, want) } @@ -1629,30 +1677,17 @@ func IGMPv3Report(expectedReport header.IGMPv3ReportSerializer) NetworkChecker { t.Errorf("got record.AuxDataLen() = %d, want = 0", got) } - if got, want := record.GroupAddress(), expectedRecords[0].GroupAddress; got != want { - t.Errorf("got record.GroupAddress() = %s, want = %s", got, want) - } - sources, ok := record.Sources() if !ok { t.Error("got record.Sources() = (_, false), want = (_, true)") continue } - expectedSources := expectedRecords[0].Sources - for len(expectedSources) != 0 { - source, ok := sources.Next() - if !ok { - t.Fatal("got sources.Next() = (_, false), want = (_, true)") - } - if source != expectedSources[0] { - t.Errorf("got sources.Next() = %s, want = %s", source, expectedSources[0]) - } - - expectedSources = expectedSources[1:] + if source, ok := sources.Next(); ok { + t.Fatalf("got sources.Next() = (%s, true), want = (_, false)", source) } - expectedRecords = expectedRecords[1:] + delete(expectedRecords, addr) } if record, res := records.Next(); res != header.IGMPv3ReportGroupAddressRecordIteratorNextDone { diff --git a/pkg/tcpip/network/internal/testutil/BUILD b/pkg/tcpip/network/internal/testutil/BUILD index 960e7f9bc..4bb8f59cb 100644 --- a/pkg/tcpip/network/internal/testutil/BUILD +++ b/pkg/tcpip/network/internal/testutil/BUILD @@ -19,6 +19,8 @@ go_library( "//pkg/tcpip", "//pkg/tcpip/checker", "//pkg/tcpip/header", + "//pkg/tcpip/link/channel", "//pkg/tcpip/stack", + "@com_github_google_go_cmp//cmp:go_default_library", ], ) diff --git a/pkg/tcpip/network/internal/testutil/testutil.go b/pkg/tcpip/network/internal/testutil/testutil.go index 40eedbc75..20113775c 100644 --- a/pkg/tcpip/network/internal/testutil/testutil.go +++ b/pkg/tcpip/network/internal/testutil/testutil.go @@ -21,10 +21,12 @@ import ( "math/rand" "testing" + "github.com/google/go-cmp/cmp" "gvisor.dev/gvisor/pkg/bufferv2" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/checker" "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/link/channel" "gvisor.dev/gvisor/pkg/tcpip/stack" ) @@ -185,43 +187,111 @@ func CheckMLDv2Stats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 ui checkMLDStats(t, s, 0 /* reports */, 0 /* leaves */, reports+leaves+reportsV2) } -// ValidateIGMPv3Report validates an IGMPv3 report. -func ValidateIGMPv3Report(t *testing.T, v *bufferv2.View, srcAddr tcpip.Address, addrs []tcpip.Address, recordType header.IGMPv3ReportRecordType) { +// ValidateIGMPv3ReportWithRecords validates an IGMPv3 report. +// +// Note that observed records are removed from expectedRecords. No error is +// logged if the report does not have all the records expected. +func ValidateIGMPv3ReportWithRecords(t *testing.T, v *bufferv2.View, srcAddr tcpip.Address, expectedRecords map[tcpip.Address]header.IGMPv3ReportRecordType) { t.Helper() - var records []header.IGMPv3ReportGroupAddressRecordSerializer - for _, addr := range addrs { - records = append(records, header.IGMPv3ReportGroupAddressRecordSerializer{ - RecordType: recordType, - GroupAddress: addr, - Sources: nil, - }) - } - checker.IPv4(t, v, checker.SrcAddr(srcAddr), checker.DstAddr(header.IGMPv3RoutersAddress), checker.TTL(header.IGMPTTL), checker.IPv4RouterAlert(), - checker.IGMPv3Report(header.IGMPv3ReportSerializer{ - Records: records, - }), + checker.IGMPv3Report(expectedRecords), ) } -// ValidateMLDv2Report validates an MLDv2 report. -func ValidateMLDv2Report(t *testing.T, v *bufferv2.View, srcAddr tcpip.Address, addrs []tcpip.Address, recordType header.MLDv2ReportRecordType) { +// ValidateIGMPv3Report validates an IGMPv3 report. +func ValidateIGMPv3Report(t *testing.T, v *bufferv2.View, srcAddr tcpip.Address, addrs []tcpip.Address, recordType header.IGMPv3ReportRecordType) { t.Helper() - var records []header.MLDv2ReportMulticastAddressRecordSerializer + records := make(map[tcpip.Address]header.IGMPv3ReportRecordType) for _, addr := range addrs { - records = append(records, header.MLDv2ReportMulticastAddressRecordSerializer{ - RecordType: recordType, - MulticastAddress: addr, - Sources: nil, - }) + records[addr] = recordType } + ValidateIGMPv3ReportWithRecords(t, v, srcAddr, records) + + if diff := cmp.Diff(map[tcpip.Address]header.IGMPv3ReportRecordType{}, records); diff != "" { + t.Errorf("post-validation records map mismatch (-want +got):\n%s", diff) + } +} + +// ValidateIGMPv3RecordsAcrossReports validates IGMPv3 records across one or +// more reports. +func ValidateIGMPv3RecordsAcrossReports(t *testing.T, e *channel.Endpoint, srcAddr tcpip.Address, addrs []tcpip.Address, recordType header.IGMPv3ReportRecordType) { + t.Helper() + + expectedRecords := make(map[tcpip.Address]header.IGMPv3ReportRecordType) + for _, addr := range addrs { + expectedRecords[addr] = recordType + } + + for len(expectedRecords) != 0 { + p := e.Read() + if p.IsNil() { + t.Fatalf("expected IGMP message with expectedRecords = %#v", expectedRecords) + } + v := stack.PayloadSince(p.NetworkHeader()) + ValidateIGMPv3ReportWithRecords(t, v, srcAddr, expectedRecords) + v.Release() + p.DecRef() + } + + if diff := cmp.Diff(map[tcpip.Address]header.IGMPv3ReportRecordType{}, expectedRecords); diff != "" { + t.Errorf("post-validation records map mismatch (-want +got):\n%s", diff) + } +} + +// ValidMultipleIGMPv2ReportLeaves validates the reception of multiple IGMPv2 +// report/leave messages. +func ValidMultipleIGMPv2ReportLeaves(t *testing.T, e *channel.Endpoint, srcAddr tcpip.Address, addrs []tcpip.Address, leave bool) { + t.Helper() + + expectedGroups := make(map[tcpip.Address]struct{}) + for _, addr := range addrs { + expectedGroups[addr] = struct{}{} + } + + igmpType := header.IGMPv2MembershipReport + if leave { + igmpType = header.IGMPLeaveGroup + } + + for len(expectedGroups) != 0 { + p := e.Read() + if p.IsNil() { + t.Fatalf("expected IGMP message with expectedGroups = %#v", expectedGroups) + } + v := stack.PayloadSince(p.NetworkHeader()) + checker.IPv4(t, v, + checker.SrcAddr(srcAddr), + checker.TTL(header.IGMPTTL), + checker.IPv4RouterAlert(), + checker.IGMP( + checker.IGMPType(igmpType), + checker.IGMPMaxRespTime(0), + checker.IGMPGroupAddressUnordered(expectedGroups), + ), + ) + v.Release() + p.DecRef() + } + + if diff := cmp.Diff(map[tcpip.Address]struct{}{}, expectedGroups); diff != "" { + t.Errorf("post-validation groups map mismatch (-want +got):\n%s", diff) + } +} + +// ValidateMLDv2ReportWithRecords validates an MLDv2 report. +// +// Note that observed records are removed from expectedRecords. No error is +// logged if the report does not have all the records expected. +func ValidateMLDv2ReportWithRecords(t *testing.T, v *bufferv2.View, srcAddr tcpip.Address, expectedRecords map[tcpip.Address]header.MLDv2ReportRecordType) { + t.Helper() + checker.IPv6WithExtHdr(t, v, checker.IPv6ExtHdr( checker.IPv6HopByHopExtensionHeader(checker.IPv6RouterAlert(header.IPv6RouterAlertMLD)), @@ -229,8 +299,89 @@ func ValidateMLDv2Report(t *testing.T, v *bufferv2.View, srcAddr tcpip.Address, checker.SrcAddr(srcAddr), checker.DstAddr(header.MLDv2RoutersAddress), checker.TTL(header.MLDHopLimit), - checker.MLDv2Report(header.MLDv2ReportSerializer{ - Records: records, - }), + checker.MLDv2Report(expectedRecords), ) } + +// ValidateMLDv2Report validates an MLDv2 report. +func ValidateMLDv2Report(t *testing.T, v *bufferv2.View, srcAddr tcpip.Address, addrs []tcpip.Address, recordType header.MLDv2ReportRecordType) { + t.Helper() + + records := make(map[tcpip.Address]header.MLDv2ReportRecordType) + for _, addr := range addrs { + records[addr] = recordType + } + + ValidateMLDv2ReportWithRecords(t, v, srcAddr, records) + + if diff := cmp.Diff(map[tcpip.Address]header.MLDv2ReportRecordType{}, records); diff != "" { + t.Errorf("post-validation records map mismatch (-want +got):\n%s", diff) + } +} + +// ValidateMLDv2RecordsAcrossReports validates MLDv2 records across one or more +// reports. +func ValidateMLDv2RecordsAcrossReports(t *testing.T, e *channel.Endpoint, srcAddr tcpip.Address, addrs []tcpip.Address, recordType header.MLDv2ReportRecordType) { + t.Helper() + + expectedRecords := make(map[tcpip.Address]header.MLDv2ReportRecordType) + for _, addr := range addrs { + expectedRecords[addr] = recordType + } + + for len(expectedRecords) != 0 { + p := e.Read() + if p.IsNil() { + t.Fatalf("expected MLD Message with expectedRecords = %#v", expectedRecords) + } + v := stack.PayloadSince(p.NetworkHeader()) + ValidateMLDv2ReportWithRecords(t, v, srcAddr, expectedRecords) + v.Release() + p.DecRef() + } + + if diff := cmp.Diff(map[tcpip.Address]header.MLDv2ReportRecordType{}, expectedRecords); diff != "" { + t.Errorf("post-validation records map mismatch (-want +got):\n%s", diff) + } +} + +// ValidMultipleMLDv1ReportLeaves validates the reception of multiple MLDv1 +// report/leave messages. +func ValidMultipleMLDv1ReportLeaves(t *testing.T, e *channel.Endpoint, srcAddr tcpip.Address, addrs []tcpip.Address, leave bool) { + t.Helper() + + expectedGroups := make(map[tcpip.Address]struct{}) + for _, addr := range addrs { + expectedGroups[addr] = struct{}{} + } + + mldType := header.ICMPv6MulticastListenerReport + if leave { + mldType = header.ICMPv6MulticastListenerDone + } + + for len(expectedGroups) != 0 { + p := e.Read() + if p.IsNil() { + t.Fatalf("expected MLD Message with expectedGroups = %#v", expectedGroups) + } + v := stack.PayloadSince(p.NetworkHeader()) + checker.IPv6WithExtHdr(t, v, + checker.IPv6ExtHdr( + checker.IPv6HopByHopExtensionHeader(checker.IPv6RouterAlert(header.IPv6RouterAlertMLD)), + ), + checker.SrcAddr(srcAddr), + checker.TTL(header.MLDHopLimit), + checker.MLD(mldType, header.MLDMinimumSize, + checker.MLDMaxRespDelay(0), + checker.MLDMulticastAddressUnordered(expectedGroups), + ), + ) + v.Release() + p.DecRef() + } + + if diff := cmp.Diff(map[tcpip.Address]struct{}{}, expectedGroups); diff != "" { + t.Errorf("post-validation groups map mismatch (-want +got):\n%s", diff) + } +} diff --git a/pkg/tcpip/network/ipv6/mld_test.go b/pkg/tcpip/network/ipv6/mld_test.go index 3b2c5873a..4ce304fd3 100644 --- a/pkg/tcpip/network/ipv6/mld_test.go +++ b/pkg/tcpip/network/ipv6/mld_test.go @@ -192,111 +192,6 @@ func TestSendQueuedMLDReports(t *testing.T) { maxReports = 2 ) - getAndCheckMLDv1MulticastAddress := func(t *testing.T, seen map[tcpip.Address]bool, p stack.PacketBufferPtr) tcpip.Address { - t.Helper() - payload := stack.PayloadSince(p.NetworkHeader()) - defer payload.Release() - ipv6 := header.IPv6(payload.AsSlice()) - - ipv6HeaderIter := header.MakeIPv6PayloadIterator( - header.IPv6ExtensionHeaderIdentifier(ipv6.NextHeader()), - bufferv2.MakeWithData(ipv6.Payload()), - ) - - var transport header.IPv6RawPayloadHeader - for { - h, done, err := ipv6HeaderIter.Next() - if err != nil { - t.Fatalf("ipv6HeaderIter.Next(): %s", err) - } - if done { - t.Fatalf("ipv6HeaderIter.Next() = (%T, %t, _), want = (_, false, _)", h, done) - } - defer h.Release() - if t, ok := h.(header.IPv6RawPayloadHeader); ok { - transport = t - break - } - } - - if got := tcpip.TransportProtocolNumber(transport.Identifier); got != header.ICMPv6ProtocolNumber { - t.Fatalf("got ipv6.NextHeader() = %d, want = %d", got, header.ICMPv6ProtocolNumber) - } - icmpv6 := header.ICMPv6(transport.Buf.Flatten()) - if got := icmpv6.Type(); got != header.ICMPv6MulticastListenerReport && got != header.ICMPv6MulticastListenerDone { - t.Fatalf("got icmpv6.Type() = %d, want = %d or %d", got, header.ICMPv6MulticastListenerReport, header.ICMPv6MulticastListenerDone) - } - addr := header.MLD(icmpv6.MessageBody()).MulticastAddress() - s, ok := seen[addr] - if !ok { - t.Fatalf("unexpectedly got a packet for group %s", addr) - } - if s { - t.Fatalf("already saw packet for group %s", addr) - } - seen[addr] = true - return addr - } - - getAndCheckMLDv2MulticastAddress := func(t *testing.T, seen map[tcpip.Address]bool, p stack.PacketBufferPtr) tcpip.Address { - t.Helper() - payload := stack.PayloadSince(p.NetworkHeader()) - defer payload.Release() - ipv6 := header.IPv6(payload.AsSlice()) - - ipv6HeaderIter := header.MakeIPv6PayloadIterator( - header.IPv6ExtensionHeaderIdentifier(ipv6.NextHeader()), - bufferv2.MakeWithData(ipv6.Payload()), - ) - - var transport header.IPv6RawPayloadHeader - for { - h, done, err := ipv6HeaderIter.Next() - if err != nil { - t.Fatalf("ipv6HeaderIter.Next(): %s", err) - } - if done { - t.Fatalf("ipv6HeaderIter.Next() = (%T, %t, _), want = (_, false, _)", h, done) - } - defer h.Release() - if t, ok := h.(header.IPv6RawPayloadHeader); ok { - transport = t - break - } - } - - if got := tcpip.TransportProtocolNumber(transport.Identifier); got != header.ICMPv6ProtocolNumber { - t.Fatalf("got ipv6.NextHeader() = %d, want = %d", got, header.ICMPv6ProtocolNumber) - } - icmpv6 := header.ICMPv6(transport.Buf.Flatten()) - if got := icmpv6.Type(); got != header.ICMPv6MulticastListenerV2Report { - t.Fatalf("got icmpv6.Type() = %d, want = %d", got, header.ICMPv6MulticastListenerV2Report) - } - - report := header.MLDv2Report(icmpv6.MessageBody()) - records := report.MulticastAddressRecords() - record, res := records.Next() - if res != header.MLDv2ReportMulticastAddressRecordIteratorNextOk { - t.Fatalf("got records.Next() = %d, want = %d", res, header.MLDv2ReportMulticastAddressRecordIteratorNextOk) - } - addr := record.MulticastAddress() - - s, ok := seen[addr] - if !ok { - t.Fatalf("unexpectedly got a packet for group %s", addr) - } - if s { - t.Fatalf("already saw packet for group %s", addr) - } - seen[addr] = true - - if _, res := records.Next(); res != header.MLDv2ReportMulticastAddressRecordIteratorNextDone { - t.Errorf("got records.Next() = %d, want = %d", res, header.MLDv2ReportMulticastAddressRecordIteratorNextDone) - } - - return addr - } - tests := []struct { name string dadTransmits uint8 @@ -315,34 +210,21 @@ func TestSendQueuedMLDReports(t *testing.T) { } subTests := []struct { - name string - v1Compatibility bool - validate func(t *testing.T, v *bufferv2.View, localAddress tcpip.Address, groupAddress tcpip.Address, leave bool) - checkStats func(*testing.T, *stack.Stack, uint64, uint64, uint64) - getAndCheckGroupAddress func(*testing.T, map[tcpip.Address]bool, stack.PacketBufferPtr) tcpip.Address + name string + v1Compatibility bool + validate func(t *testing.T, e *channel.Endpoint, localAddress tcpip.Address, groupAddresses []tcpip.Address, leave bool) + checkStats func(*testing.T, *stack.Stack, uint64, uint64, uint64) }{ { name: "V1 Compatibility", v1Compatibility: true, - validate: func(t *testing.T, v *bufferv2.View, localAddress tcpip.Address, groupAddress tcpip.Address, leave bool) { - t.Helper() - - remoteAddress := groupAddress - icmpType := header.ICMPv6MulticastListenerReport - if leave { - remoteAddress = header.IPv6AllRoutersLinkLocalMulticastAddress - icmpType = header.ICMPv6MulticastListenerDone - } - - validateMLDPacket(t, v, localAddress, remoteAddress, icmpType, groupAddress) - }, - checkStats: iptestutil.CheckMLDv1Stats, - getAndCheckGroupAddress: getAndCheckMLDv1MulticastAddress, + validate: iptestutil.ValidMultipleMLDv1ReportLeaves, + checkStats: iptestutil.CheckMLDv1Stats, }, { name: "V2", v1Compatibility: false, - validate: func(t *testing.T, v *bufferv2.View, localAddress tcpip.Address, groupAddress tcpip.Address, leave bool) { + validate: func(t *testing.T, e *channel.Endpoint, localAddress tcpip.Address, groupAddresses []tcpip.Address, leave bool) { t.Helper() recordType := header.MLDv2ReportRecordChangeToExcludeMode @@ -350,10 +232,9 @@ func TestSendQueuedMLDReports(t *testing.T) { recordType = header.MLDv2ReportRecordChangeToIncludeMode } - validateMLDv2ReportPacket(t, v, localAddress, groupAddress, recordType) + iptestutil.ValidateMLDv2RecordsAcrossReports(t, e, localAddress, groupAddresses, recordType) }, - checkStats: iptestutil.CheckMLDv2Stats, - getAndCheckGroupAddress: getAndCheckMLDv2MulticastAddress, + checkStats: iptestutil.CheckMLDv2Stats, }, } @@ -452,12 +333,7 @@ func TestSendQueuedMLDReports(t *testing.T) { } reportCounter++ subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter) - if p := e.Read(); p.IsNil() { - t.Errorf("expected MLD report for %s", globalMulticastAddr) - } else { - subTest.validate(t, stack.PayloadSince(p.NetworkHeader()), header.IPv6Any, globalMulticastAddr, false /* leave */) - p.DecRef() - } + subTest.validate(t, e, header.IPv6Any, []tcpip.Address{globalMulticastAddr}, false /* leave */) clock.Advance(time.Hour) checkVersion() if p := e.Read(); !p.IsNil() { @@ -485,12 +361,7 @@ func TestSendQueuedMLDReports(t *testing.T) { } reportCounter++ subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter) - if p := e.Read(); p.IsNil() { - t.Errorf("expected MLD report for %s", globalAddrSNMC) - } else { - subTest.validate(t, stack.PayloadSince(p.NetworkHeader()), header.IPv6Any, globalAddrSNMC, false /* leave */) - p.DecRef() - } + subTest.validate(t, e, header.IPv6Any, []tcpip.Address{globalAddrSNMC}, false /* leave */) if dadResolutionTime != 0 { // Reports should not be sent when the address resolves. resolveDAD(globalAddr, globalAddrSNMC) @@ -504,12 +375,7 @@ func TestSendQueuedMLDReports(t *testing.T) { if !subTest.v1Compatibility { doneCounter++ subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter) - if p := e.Read(); p.IsNil() { - t.Errorf("expected MLD report for %s", globalAddrSNMC) - } else { - subTest.validate(t, stack.PayloadSince(p.NetworkHeader()), header.IPv6Any, globalAddrSNMC, true /* leave */) - p.DecRef() - } + subTest.validate(t, e, header.IPv6Any, []tcpip.Address{globalAddrSNMC}, true /* leave */) } subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter) if p := e.Read(); !p.IsNil() { @@ -531,12 +397,7 @@ func TestSendQueuedMLDReports(t *testing.T) { } if dadResolutionTime != 0 { reportCounter++ - if p := e.Read(); p.IsNil() { - t.Errorf("expected MLD report for %s", linkLocalAddrSNMC) - } else { - subTest.validate(t, stack.PayloadSince(p.NetworkHeader()), header.IPv6Any, linkLocalAddrSNMC, false /* leave */) - p.DecRef() - } + subTest.validate(t, e, header.IPv6Any, []tcpip.Address{linkLocalAddrSNMC}, false /* leave */) resolveDAD(linkLocalAddr, linkLocalAddrSNMC) } @@ -549,28 +410,15 @@ func TestSendQueuedMLDReports(t *testing.T) { reportCounter += maxReports subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter) - addrs := map[tcpip.Address]bool{ - globalMulticastAddr: false, - linkLocalAddrSNMC: false, - } - for range addrs { - p := e.Read() - if p.IsNil() { - t.Fatalf("expected MLD report for %s and %s; addrs = %#v", globalMulticastAddr, linkLocalAddrSNMC, addrs) - } + subTest.validate( + t, + e, + linkLocalAddr, + []tcpip.Address{globalMulticastAddr, linkLocalAddrSNMC}, + false, /* leave */ + ) - subTest.validate( - t, - stack.PayloadSince(p.NetworkHeader()), - linkLocalAddr, - subTest.getAndCheckGroupAddress(t, addrs, p), - false, /* leave */ - ) - - p.DecRef() - - clock.Advance(ipv6.UnsolicitedReportIntervalMax) - } + clock.Advance(ipv6.UnsolicitedReportIntervalMax) } // Should not send any more reports. diff --git a/pkg/tcpip/network/multicast_group_test.go b/pkg/tcpip/network/multicast_group_test.go index a8470ebdb..507f0259f 100644 --- a/pkg/tcpip/network/multicast_group_test.go +++ b/pkg/tcpip/network/multicast_group_test.go @@ -1234,176 +1234,12 @@ func TestMGPReportMessages(t *testing.T) { func TestMGPWithNICLifecycle(t *testing.T) { type subTest struct { - name string - v1Compatibility bool - enterVersion func(e *channel.Endpoint) - validateReport func(*testing.T, stack.PacketBufferPtr, tcpip.Address) - validateLeave func(*testing.T, stack.PacketBufferPtr, []tcpip.Address) - checkStats func(*testing.T, *stack.Stack, uint64, uint64, uint64) - getAndCheckGroupAddress func(*testing.T, map[tcpip.Address]bool, stack.PacketBufferPtr) []tcpip.Address - } - - getAndCheckIGMPv2GroupAddress := func(t *testing.T, seen map[tcpip.Address]bool, p stack.PacketBufferPtr) []tcpip.Address { - t.Helper() - - payload := stack.PayloadSince(p.NetworkHeader()) - defer payload.Release() - ipv4 := header.IPv4(payload.AsSlice()) - if got := tcpip.TransportProtocolNumber(ipv4.Protocol()); got != header.IGMPProtocolNumber { - t.Fatalf("got ipv4.Protocol() = %d, want = %d", got, header.IGMPProtocolNumber) - } - addr := header.IGMP(ipv4.Payload()).GroupAddress() - s, ok := seen[addr] - if !ok { - t.Fatalf("unexpectedly got a packet for group %s", addr) - } - if s { - t.Fatalf("already saw packet for group %s", addr) - } - seen[addr] = true - return []tcpip.Address{addr} - } - - getAndCheckIGMPv3GroupAddress := func(t *testing.T, seen map[tcpip.Address]bool, p stack.PacketBufferPtr) []tcpip.Address { - t.Helper() - - payload := stack.PayloadSince(p.NetworkHeader()) - defer payload.Release() - ipv4 := header.IPv4(payload.AsSlice()) - if got := tcpip.TransportProtocolNumber(ipv4.Protocol()); got != header.IGMPProtocolNumber { - t.Fatalf("got ipv4.Protocol() = %d, want = %d", got, header.IGMPProtocolNumber) - } - report := header.IGMPv3Report(ipv4.Payload()) - records := report.GroupAddressRecords() - var addrs []tcpip.Address - for { - record, res := records.Next() - switch res { - case header.IGMPv3ReportGroupAddressRecordIteratorNextOk: - case header.IGMPv3ReportGroupAddressRecordIteratorNextDone: - return addrs - default: - t.Fatalf("unhandled res = %d", res) - } - addr := record.GroupAddress() - - if s, ok := seen[addr]; !ok { - t.Fatalf("unexpectedly got a packet for group %s", addr) - } else if s { - t.Fatalf("already saw packet for group %s", addr) - } - seen[addr] = true - - addrs = append(addrs, addr) - } - } - - getAndCheckMLDv1MulticastAddress := func(t *testing.T, seen map[tcpip.Address]bool, p stack.PacketBufferPtr) []tcpip.Address { - t.Helper() - payload := stack.PayloadSince(p.NetworkHeader()) - defer payload.Release() - ipv6 := header.IPv6(payload.AsSlice()) - - ipv6HeaderIter := header.MakeIPv6PayloadIterator( - header.IPv6ExtensionHeaderIdentifier(ipv6.NextHeader()), - bufferv2.MakeWithData(ipv6.Payload()), - ) - - var transport header.IPv6RawPayloadHeader - for { - h, done, err := ipv6HeaderIter.Next() - if err != nil { - t.Fatalf("ipv6HeaderIter.Next(): %s", err) - } - if done { - t.Fatalf("ipv6HeaderIter.Next() = (%T, %t, _), want = (_, false, _)", h, done) - } - defer h.Release() - if t, ok := h.(header.IPv6RawPayloadHeader); ok { - transport = t - break - } - } - - if got := tcpip.TransportProtocolNumber(transport.Identifier); got != header.ICMPv6ProtocolNumber { - t.Fatalf("got ipv6.NextHeader() = %d, want = %d", got, header.ICMPv6ProtocolNumber) - } - icmpv6 := header.ICMPv6(transport.Buf.Flatten()) - if got := icmpv6.Type(); got != header.ICMPv6MulticastListenerReport && got != header.ICMPv6MulticastListenerDone { - t.Fatalf("got icmpv6.Type() = %d, want = %d or %d", got, header.ICMPv6MulticastListenerReport, header.ICMPv6MulticastListenerDone) - } - addr := header.MLD(icmpv6.MessageBody()).MulticastAddress() - s, ok := seen[addr] - if !ok { - t.Fatalf("unexpectedly got a packet for group %s", addr) - } - if s { - t.Fatalf("already saw packet for group %s", addr) - } - seen[addr] = true - return []tcpip.Address{addr} - } - - getAndCheckMLDv2MulticastAddress := func(t *testing.T, seen map[tcpip.Address]bool, p stack.PacketBufferPtr) []tcpip.Address { - t.Helper() - - payload := stack.PayloadSince(p.NetworkHeader()) - defer payload.Release() - ipv6 := header.IPv6(payload.AsSlice()) - - ipv6HeaderIter := header.MakeIPv6PayloadIterator( - header.IPv6ExtensionHeaderIdentifier(ipv6.NextHeader()), - bufferv2.MakeWithData(ipv6.Payload()), - ) - - var transport header.IPv6RawPayloadHeader - for { - h, done, err := ipv6HeaderIter.Next() - if err != nil { - t.Fatalf("ipv6HeaderIter.Next(): %s", err) - } - if done { - t.Fatalf("ipv6HeaderIter.Next() = (%T, %t, _), want = (_, false, _)", h, done) - } - defer h.Release() - if t, ok := h.(header.IPv6RawPayloadHeader); ok { - transport = t - break - } - } - - if got := tcpip.TransportProtocolNumber(transport.Identifier); got != header.ICMPv6ProtocolNumber { - t.Fatalf("got ipv6.NextHeader() = %d, want = %d", got, header.ICMPv6ProtocolNumber) - } - icmpv6 := header.ICMPv6(transport.Buf.Flatten()) - if got := icmpv6.Type(); got != header.ICMPv6MulticastListenerV2Report { - t.Fatalf("got icmpv6.Type() = %d, want = %d", got, header.ICMPv6MulticastListenerV2Report) - } - - report := header.MLDv2Report(icmpv6.MessageBody()) - records := report.MulticastAddressRecords() - var addrs []tcpip.Address - for { - record, res := records.Next() - switch res { - case header.MLDv2ReportMulticastAddressRecordIteratorNextOk: - case header.MLDv2ReportMulticastAddressRecordIteratorNextDone: - return addrs - default: - t.Fatalf("unhandled res = %d", res) - } - - addr := record.MulticastAddress() - s, ok := seen[addr] - if !ok { - t.Fatalf("unexpectedly got a packet for group %s", addr) - } - if s { - t.Fatalf("already saw packet for group %s", addr) - } - seen[addr] = true - addrs = append(addrs, addr) - } + name string + v1Compatibility bool + enterVersion func(e *channel.Endpoint) + validateReport func(*testing.T, stack.PacketBufferPtr, tcpip.Address) + validateLeave func(*testing.T, *channel.Endpoint, []tcpip.Address) + checkStats func(*testing.T, *stack.Stack, uint64, uint64, uint64) } tests := []struct { @@ -1414,9 +1250,8 @@ func TestMGPWithNICLifecycle(t *testing.T) { maxUnsolicitedResponseDelay time.Duration sentReportStat func(*stack.Stack) *tcpip.StatCounter sentLeaveStat func(*stack.Stack) *tcpip.StatCounter - validateReport func(*testing.T, stack.PacketBufferPtr, []tcpip.Address) + validateReport func(*testing.T, *channel.Endpoint, []tcpip.Address) validateLeave func(*testing.T, stack.PacketBufferPtr, tcpip.Address) - getAndCheckGroupAddress func(*testing.T, map[tcpip.Address]bool, stack.PacketBufferPtr) []tcpip.Address checkInitialGroups func(*testing.T, *channel.Endpoint, *stack.Stack, *faketime.ManualClock) uint64 checkStats func(*testing.T, *stack.Stack, uint64, uint64, uint64) subTests []subTest @@ -1433,18 +1268,16 @@ func TestMGPWithNICLifecycle(t *testing.T) { sentLeaveStat: func(s *stack.Stack) *tcpip.StatCounter { return s.Stats().IGMP.PacketsSent.LeaveGroup }, - validateReport: func(t *testing.T, p stack.PacketBufferPtr, addrs []tcpip.Address) { + validateReport: func(t *testing.T, e *channel.Endpoint, addrs []tcpip.Address) { t.Helper() - - validateIGMPv3ReportPacket(t, p, addrs, header.IGMPv3ReportRecordChangeToExcludeMode) + iptestutil.ValidateIGMPv3RecordsAcrossReports(t, e, stackIPv4Addr, addrs, header.IGMPv3ReportRecordChangeToExcludeMode) }, validateLeave: func(t *testing.T, p stack.PacketBufferPtr, addr tcpip.Address) { t.Helper() validateIGMPv3ReportPacket(t, p, []tcpip.Address{addr}, header.IGMPv3ReportRecordChangeToIncludeMode) }, - getAndCheckGroupAddress: getAndCheckIGMPv3GroupAddress, - checkStats: iptestutil.CheckIGMPv3Stats, + checkStats: iptestutil.CheckIGMPv3Stats, subTests: []subTest{ { name: "V2", @@ -1458,13 +1291,11 @@ func TestMGPWithNICLifecycle(t *testing.T) { validateIGMPPacket(t, p, addr, igmpv2MembershipReport, 0, addr) }, - validateLeave: func(t *testing.T, p stack.PacketBufferPtr, addrs []tcpip.Address) { + validateLeave: func(t *testing.T, e *channel.Endpoint, addrs []tcpip.Address) { t.Helper() - - validateIGMPPacket(t, p, header.IPv4AllRoutersGroup, igmpLeaveGroup, 0, addrs[0]) + iptestutil.ValidMultipleIGMPv2ReportLeaves(t, e, stackIPv4Addr, addrs, true /* leave */) }, - checkStats: iptestutil.CheckIGMPv2Stats, - getAndCheckGroupAddress: getAndCheckIGMPv2GroupAddress, + checkStats: iptestutil.CheckIGMPv2Stats, }, { name: "V3", @@ -1475,13 +1306,11 @@ func TestMGPWithNICLifecycle(t *testing.T) { validateIGMPv3ReportPacket(t, p, []tcpip.Address{addr}, header.IGMPv3ReportRecordChangeToExcludeMode) }, - validateLeave: func(t *testing.T, p stack.PacketBufferPtr, addrs []tcpip.Address) { + validateLeave: func(t *testing.T, e *channel.Endpoint, addrs []tcpip.Address) { t.Helper() - - validateIGMPv3ReportPacket(t, p, addrs, header.IGMPv3ReportRecordChangeToIncludeMode) + iptestutil.ValidateIGMPv3RecordsAcrossReports(t, e, stackIPv4Addr, addrs, header.IGMPv3ReportRecordChangeToIncludeMode) }, - checkStats: iptestutil.CheckIGMPv3Stats, - getAndCheckGroupAddress: getAndCheckIGMPv3GroupAddress, + checkStats: iptestutil.CheckIGMPv3Stats, }, }, }, @@ -1497,18 +1326,18 @@ func TestMGPWithNICLifecycle(t *testing.T) { sentLeaveStat: func(s *stack.Stack) *tcpip.StatCounter { return s.Stats().ICMP.V6.PacketsSent.MulticastListenerDone }, - validateReport: func(t *testing.T, p stack.PacketBufferPtr, addrs []tcpip.Address) { + validateReport: func(t *testing.T, e *channel.Endpoint, addrs []tcpip.Address) { t.Helper() - validateMLDv2ReportPacket(t, p, addrs, header.MLDv2ReportRecordChangeToExcludeMode) + + iptestutil.ValidateMLDv2RecordsAcrossReports(t, e, linkLocalIPv6Addr1, addrs, header.MLDv2ReportRecordChangeToExcludeMode) }, validateLeave: func(t *testing.T, p stack.PacketBufferPtr, addr tcpip.Address) { t.Helper() validateMLDv2ReportPacket(t, p, []tcpip.Address{addr}, header.MLDv2ReportRecordChangeToIncludeMode) }, - getAndCheckGroupAddress: getAndCheckMLDv2MulticastAddress, - checkInitialGroups: checkInitialIPv6Groups, - checkStats: iptestutil.CheckMLDv2Stats, + checkInitialGroups: checkInitialIPv6Groups, + checkStats: iptestutil.CheckMLDv2Stats, subTests: []subTest{ { name: "V1", @@ -1522,13 +1351,12 @@ func TestMGPWithNICLifecycle(t *testing.T) { validateMLDPacket(t, p, addr, mldReport, 0, addr) }, - validateLeave: func(t *testing.T, p stack.PacketBufferPtr, addrs []tcpip.Address) { + validateLeave: func(t *testing.T, e *channel.Endpoint, addrs []tcpip.Address) { t.Helper() - validateMLDPacket(t, p, header.IPv6AllRoutersLinkLocalMulticastAddress, mldDone, 0, addrs[0]) + iptestutil.ValidMultipleMLDv1ReportLeaves(t, e, linkLocalIPv6Addr1, addrs, true /* leave */) }, - checkStats: iptestutil.CheckMLDv1Stats, - getAndCheckGroupAddress: getAndCheckMLDv1MulticastAddress, + checkStats: iptestutil.CheckMLDv1Stats, }, { name: "V2", @@ -1539,13 +1367,12 @@ func TestMGPWithNICLifecycle(t *testing.T) { validateMLDv2ReportPacket(t, p, []tcpip.Address{addr}, header.MLDv2ReportRecordChangeToExcludeMode) }, - validateLeave: func(t *testing.T, p stack.PacketBufferPtr, addrs []tcpip.Address) { + validateLeave: func(t *testing.T, e *channel.Endpoint, addrs []tcpip.Address) { t.Helper() - validateMLDv2ReportPacket(t, p, addrs, header.MLDv2ReportRecordChangeToIncludeMode) + iptestutil.ValidateMLDv2RecordsAcrossReports(t, e, linkLocalIPv6Addr1, addrs, header.MLDv2ReportRecordChangeToIncludeMode) }, - checkStats: iptestutil.CheckMLDv2Stats, - getAndCheckGroupAddress: getAndCheckMLDv2MulticastAddress, + checkStats: iptestutil.CheckMLDv2Stats, }, }, }, @@ -1597,20 +1424,7 @@ func TestMGPWithNICLifecycle(t *testing.T) { } leaveCounter += uint64(numMessages) subTest.checkStats(t, s, reportCounter, leaveCounter, reportV2Counter) - seen := make(map[tcpip.Address]bool) - for _, a := range test.multicastAddrs { - seen[a] = false - } - - for i := 0; i < numMessages; i++ { - p := e.Read() - if p.IsNil() { - t.Fatalf("expected (%d-th) leave message to be sent", i) - } - - subTest.validateLeave(t, p, subTest.getAndCheckGroupAddress(t, seen, p)) - p.DecRef() - } + subTest.validateLeave(t, e, test.multicastAddrs) } if t.Failed() { t.FailNow() @@ -1622,22 +1436,7 @@ func TestMGPWithNICLifecycle(t *testing.T) { } reportV2Counter += uint64(len(test.multicastAddrs)) subTest.checkStats(t, s, reportCounter, leaveCounter, reportV2Counter) - { - seen := make(map[tcpip.Address]bool) - for _, a := range test.multicastAddrs { - seen[a] = false - } - - for i := range test.multicastAddrs { - p := e.Read() - if p.IsNil() { - t.Fatalf("expected (%d-th) report message to be sent", i) - } - - test.validateReport(t, p, test.getAndCheckGroupAddress(t, seen, p)) - p.DecRef() - } - } + test.validateReport(t, e, test.multicastAddrs) if t.Failed() { t.FailNow() } @@ -1678,22 +1477,12 @@ func TestMGPWithNICLifecycle(t *testing.T) { } reportV2Counter++ subTest.checkStats(t, s, reportCounter, leaveCounter, reportV2Counter) - if p := e.Read(); p.IsNil() { - t.Fatal("expected a report message to be sent") - } else { - test.validateReport(t, p, []tcpip.Address{test.finalMulticastAddr}) - p.DecRef() - } + test.validateReport(t, e, []tcpip.Address{test.finalMulticastAddr}) clock.Advance(test.maxUnsolicitedResponseDelay) reportV2Counter++ subTest.checkStats(t, s, reportCounter, leaveCounter, reportV2Counter) - if p := e.Read(); p.IsNil() { - t.Fatal("expected a report message to be sent") - } else { - test.validateReport(t, p, []tcpip.Address{test.finalMulticastAddr}) - p.DecRef() - } + test.validateReport(t, e, []tcpip.Address{test.finalMulticastAddr}) // Should not send any more packets. clock.Advance(time.Hour) @@ -1766,10 +1555,7 @@ func TestMGPDisabledOnLoopback(t *testing.T) { } func TestMGPCoalescedQueryResponseRecords(t *testing.T) { - const ( - extraGroups = 1 - igmpv3MLDv2ReportRecordHeaderLen = 4 - ) + const igmpv3MLDv2ReportRecordHeaderLen = 4 type subTest struct { name string @@ -1799,7 +1585,7 @@ func TestMGPCoalescedQueryResponseRecords(t *testing.T) { genAddr func(uint16) tcpip.Address maxRecordsPerMessage uint16 rxQuery func(*channel.Endpoint, uint8) - validateReportWithMultipleRecords func(*testing.T, map[tcpip.Address]bool, stack.PacketBufferPtr, uint16) + validateReportWithMultipleRecords func(*testing.T, *channel.Endpoint, []tcpip.Address) }{ { name: "IGMP", @@ -1825,42 +1611,9 @@ func TestMGPCoalescedQueryResponseRecords(t *testing.T) { rxQuery: func(e *channel.Endpoint, maxRespTime uint8) { createAndInjectIGMPPacket(e, igmpMembershipQuery, maxRespTime, header.IPv4Any, header.IGMPv3QueryMinimumSize-header.IGMPQueryMinimumSize /* extraLength */) }, - validateReportWithMultipleRecords: func(t *testing.T, seen map[tcpip.Address]bool, p stack.PacketBufferPtr, expectedRecords uint16) { + validateReportWithMultipleRecords: func(t *testing.T, e *channel.Endpoint, addrs []tcpip.Address) { t.Helper() - - payload := stack.PayloadSince(p.NetworkHeader()) - defer payload.Release() - ipv4 := header.IPv4(payload.AsSlice()) - if got := tcpip.TransportProtocolNumber(ipv4.Protocol()); got != header.IGMPProtocolNumber { - t.Fatalf("got ipv4.Protocol() = %d, want = %d", got, header.IGMPProtocolNumber) - } - report := header.IGMPv3Report(ipv4.Payload()) - records := report.GroupAddressRecords() - for recordsCount := uint16(0); ; recordsCount++ { - record, res := records.Next() - switch res { - case header.IGMPv3ReportGroupAddressRecordIteratorNextOk: - case header.IGMPv3ReportGroupAddressRecordIteratorNextDone: - if recordsCount != expectedRecords { - t.Errorf("got recordsCount = %d, want = %d", recordsCount, expectedRecords) - } - return - default: - t.Fatalf("records.Next(): %d", res) - } - - if res != header.IGMPv3ReportGroupAddressRecordIteratorNextOk { - t.Fatalf("got records.Next() = %d, want = %d", res, header.IGMPv3ReportGroupAddressRecordIteratorNextOk) - } - addr := record.GroupAddress() - - if s, ok := seen[addr]; !ok { - t.Fatalf("unexpectedly got a packet for group %s", addr) - } else if s { - t.Fatalf("already saw packet for group %s", addr) - } - seen[addr] = true - } + iptestutil.ValidateIGMPv3RecordsAcrossReports(t, e, stackIPv4Addr, addrs, header.IGMPv3ReportRecordModeIsExclude) }, }, { @@ -1888,146 +1641,101 @@ func TestMGPCoalescedQueryResponseRecords(t *testing.T) { rxQuery: func(e *channel.Endpoint, maxRespTime uint8) { createAndInjectMLDPacket(e, mldQuery, maxRespTime, header.IPv6Any, header.MLDv2QueryMinimumSize-header.MLDMinimumSize /* extraLength */) }, - validateReportWithMultipleRecords: func(t *testing.T, seen map[tcpip.Address]bool, p stack.PacketBufferPtr, expectedRecords uint16) { + validateReportWithMultipleRecords: func(t *testing.T, e *channel.Endpoint, addrs []tcpip.Address) { t.Helper() - payload := stack.PayloadSince(p.NetworkHeader()) - defer payload.Release() - ipv6 := header.IPv6(payload.AsSlice()) - - ipv6HeaderIter := header.MakeIPv6PayloadIterator( - header.IPv6ExtensionHeaderIdentifier(ipv6.NextHeader()), - bufferv2.MakeWithData(ipv6.Payload()), - ) - - var transport header.IPv6RawPayloadHeader - for { - h, done, err := ipv6HeaderIter.Next() - if err != nil { - t.Fatalf("ipv6HeaderIter.Next(): %s", err) - } - if done { - t.Fatalf("ipv6HeaderIter.Next() = (%T, %t, _), want = (_, false, _)", h, done) - } - defer h.Release() - if t, ok := h.(header.IPv6RawPayloadHeader); ok { - transport = t - break - } - } - - if got := tcpip.TransportProtocolNumber(transport.Identifier); got != header.ICMPv6ProtocolNumber { - t.Fatalf("got ipv6.NextHeader() = %d, want = %d", got, header.ICMPv6ProtocolNumber) - } - icmpv6 := header.ICMPv6(transport.Buf.Flatten()) - if got := icmpv6.Type(); got != header.ICMPv6MulticastListenerV2Report { - t.Fatalf("got icmpv6.Type() = %d, want = %d", got, header.ICMPv6MulticastListenerV2Report) - } - - report := header.MLDv2Report(icmpv6.MessageBody()) - records := report.MulticastAddressRecords() - for recordsCount := uint16(0); ; recordsCount++ { - record, res := records.Next() - switch res { - case header.MLDv2ReportMulticastAddressRecordIteratorNextOk: - case header.MLDv2ReportMulticastAddressRecordIteratorNextDone: - if recordsCount != expectedRecords { - t.Errorf("got recordsCount = %d, want = %d", recordsCount, expectedRecords) - } - return - default: - t.Fatalf("records.Next(): %d", res) - } - - addr := record.MulticastAddress() - - s, ok := seen[addr] - if !ok { - t.Fatalf("unexpectedly got a packet for group %s", addr) - } - if s { - t.Fatalf("already saw packet for group %s", addr) - } - seen[addr] = true - } + iptestutil.ValidateMLDv2RecordsAcrossReports(t, e, linkLocalIPv6Addr1, addrs, header.MLDv2ReportRecordModeIsExclude) }, }, } + subTests := []struct { + name string + extraRecords uint16 + expectedReports uint64 + }{ + { + name: "No extra records", + extraRecords: 0, + expectedReports: 1, + }, + { + name: "One extra record", + extraRecords: 1, + expectedReports: 2, + }, + { + name: "Two extra records", + extraRecords: 2, + expectedReports: 2, + }, + } + for _, test := range tests { t.Run(test.name, func(t *testing.T) { - ctx := newMulticastTestContext(t, test.protoNum == ipv4.ProtocolNumber /* v4 */, true /* mgpEnabled */) - defer ctx.cleanup() - s, e, clock := ctx.s, ctx.e, ctx.clock + for _, subTest := range subTests { + t.Run(subTest.name, func(t *testing.T) { + ctx := newMulticastTestContext(t, test.protoNum == ipv4.ProtocolNumber /* v4 */, true /* mgpEnabled */) + defer ctx.cleanup() + s, e, clock := ctx.s, ctx.e, ctx.clock - var reportV2Counter uint64 - if test.checkInitialGroups != nil { - reportV2Counter = test.checkInitialGroups(t, e, s, clock) - } + var reportV2Counter uint64 + if test.checkInitialGroups != nil { + reportV2Counter = test.checkInitialGroups(t, e, s, clock) + } - seen := make(map[tcpip.Address]bool) - for i := uint16(0); i < test.maxRecordsPerMessage+extraGroups; i++ { - addr := test.genAddr(i) - seen[addr] = false + addrs := make([]tcpip.Address, test.maxRecordsPerMessage+subTest.extraRecords) + for i := 0; i < len(addrs); i++ { + addr := test.genAddr(uint16(i)) + addrs[i] = addr - if err := s.JoinGroup(test.protoNum, nicID, addr); err != nil { - t.Fatalf("JoinGroup(%d, %d, %s): %s", test.protoNum, nicID, addr, err) - } - reportV2Counter++ - test.checkStats(t, s, reportV2Counter) - if p := e.Read(); p.IsNil() { - t.Fatal("expected a report message to be sent") - } else { - test.validateReport(t, p, addr) - p.DecRef() - } - if t.Failed() { - t.FailNow() - } + if err := s.JoinGroup(test.protoNum, nicID, addr); err != nil { + t.Fatalf("JoinGroup(%d, %d, %s): %s", test.protoNum, nicID, addr, err) + } + reportV2Counter++ + test.checkStats(t, s, reportV2Counter) + if p := e.Read(); p.IsNil() { + t.Fatal("expected a report message to be sent") + } else { + test.validateReport(t, p, addr) + p.DecRef() + } + if t.Failed() { + t.FailNow() + } - // Verify the second report is sent by the maximum unsolicited response - // interval. - p := e.Read() - if !p.IsNil() { - t.Fatalf("sent unexpected packet, expected report only after advancing the clock = %#v", p) - } - clock.Advance(test.maxUnsolicitedResponseDelay) - reportV2Counter++ - test.checkStats(t, s, reportV2Counter) - if p := e.Read(); p.IsNil() { - t.Fatal("expected a report message to be sent") - } else { - test.validateReport(t, p, addr) - p.DecRef() - } - } + // Verify the second report is sent by the maximum unsolicited response + // interval. + p := e.Read() + if !p.IsNil() { + t.Fatalf("sent unexpected packet, expected report only after advancing the clock = %#v", p) + } + clock.Advance(test.maxUnsolicitedResponseDelay) + reportV2Counter++ + test.checkStats(t, s, reportV2Counter) + if p := e.Read(); p.IsNil() { + t.Fatal("expected a report message to be sent") + } else { + test.validateReport(t, p, addr) + p.DecRef() + } + } - // Should not send any more packets. - clock.Advance(time.Hour) - if p := e.Read(); !p.IsNil() { - t.Fatalf("sent unexpected packet = %#v", p) - } - test.checkStats(t, s, reportV2Counter) + // Should not send any more packets. + clock.Advance(time.Hour) + if p := e.Read(); !p.IsNil() { + t.Fatalf("sent unexpected packet = %#v", p) + } + test.checkStats(t, s, reportV2Counter) - // Receive a query which should send a few reports which together hold - // records for all the groups we joined. - test.rxQuery(e, 1) - clock.Advance(time.Second) - reportV2Counter += 2 - test.checkStats(t, s, reportV2Counter) - for _, expectedRecords := range []uint16{test.maxRecordsPerMessage, extraGroups} { - if p := e.Read(); p.IsNil() { - t.Fatal("expected a report message to be sent") - } else { - test.validateReportWithMultipleRecords(t, seen, p, expectedRecords) - p.DecRef() - } - } - - for addr, seen := range seen { - if !seen { - t.Errorf("got seen[%s] = false, want = true", addr) - } + // Receive a query which should send a few reports which together hold + // records for all the groups we joined. + test.rxQuery(e, 1) + clock.Advance(time.Second) + reportV2Counter += subTest.expectedReports + test.checkStats(t, s, reportV2Counter) + test.validateReportWithMultipleRecords(t, e, addrs) + }) } }) }