// Copyright 2020 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Package testutil defines types and functions used to test Network Layer // functionality such as IP fragmentation. package testutil import ( "fmt" "testing" "github.com/google/go-cmp/cmp" "gvisor.dev/gvisor/pkg/buffer" "gvisor.dev/gvisor/pkg/rand" "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" ) // MockLinkEndpoint is an endpoint used for testing, it stores packets written // to it and can mock errors. type MockLinkEndpoint struct { // WrittenPackets is where packets written to the endpoint are stored. WrittenPackets []*stack.PacketBuffer mtu uint32 err tcpip.Error allowPackets int } // NewMockLinkEndpoint creates a new MockLinkEndpoint. // // err is the error that will be returned once allowPackets packets are written // to the endpoint. func NewMockLinkEndpoint(mtu uint32, err tcpip.Error, allowPackets int) *MockLinkEndpoint { return &MockLinkEndpoint{ mtu: mtu, err: err, allowPackets: allowPackets, } } // MTU implements LinkEndpoint.MTU. func (ep *MockLinkEndpoint) MTU() uint32 { return ep.mtu } // SetMTU implements LinkEndpoint.SetMTU. func (ep *MockLinkEndpoint) SetMTU(mtu uint32) { ep.mtu = mtu } // Capabilities implements LinkEndpoint.Capabilities. func (*MockLinkEndpoint) Capabilities() stack.LinkEndpointCapabilities { return 0 } // MaxHeaderLength implements LinkEndpoint.MaxHeaderLength. func (*MockLinkEndpoint) MaxHeaderLength() uint16 { return 0 } // LinkAddress implements LinkEndpoint.LinkAddress. func (*MockLinkEndpoint) LinkAddress() tcpip.LinkAddress { return "" } // SetLinkAddress implements LinkEndpoint.LinkAddress. func (*MockLinkEndpoint) SetLinkAddress(tcpip.LinkAddress) {} // WritePackets implements LinkEndpoint.WritePackets. func (ep *MockLinkEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) { var n int for _, pkt := range pkts.AsSlice() { if ep.allowPackets == 0 { return n, ep.err } ep.allowPackets-- ep.WrittenPackets = append(ep.WrittenPackets, pkt.Clone()) n++ } return n, nil } // Attach implements LinkEndpoint.Attach. func (*MockLinkEndpoint) Attach(stack.NetworkDispatcher) {} // IsAttached implements LinkEndpoint.IsAttached. func (*MockLinkEndpoint) IsAttached() bool { return false } // Wait implements LinkEndpoint.Wait. func (*MockLinkEndpoint) Wait() {} // ARPHardwareType implements LinkEndpoint.ARPHardwareType. func (*MockLinkEndpoint) ARPHardwareType() header.ARPHardwareType { return header.ARPHardwareNone } // AddHeader implements LinkEndpoint.AddHeader. func (*MockLinkEndpoint) AddHeader(*stack.PacketBuffer) {} // ParseHeader implements LinkEndpoint.ParseHeader. func (*MockLinkEndpoint) ParseHeader(*stack.PacketBuffer) bool { return true } // Close releases all resources. func (ep *MockLinkEndpoint) Close() { for _, pkt := range ep.WrittenPackets { pkt.DecRef() } ep.WrittenPackets = nil } // SetOnCloseAction implements stack.LinkEndpoint.SetOnCloseAction. func (*MockLinkEndpoint) SetOnCloseAction(func()) {} // MakeRandPkt generates a randomized packet. transportHeaderLength indicates // how many random bytes will be copied in the Transport Header. // extraHeaderReserveLength indicates how much extra space will be reserved for // the other headers. The payload is made from Views of the sizes listed in // viewSizes. func MakeRandPkt(transportHeaderLength int, extraHeaderReserveLength int, viewSizes []int, proto tcpip.NetworkProtocolNumber) *stack.PacketBuffer { var buf buffer.Buffer for _, s := range viewSizes { newView := buffer.NewViewSize(s) if _, err := rand.Read(newView.AsSlice()); err != nil { panic(fmt.Sprintf("rand.Read: %s", err)) } buf.Append(newView) } pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{ ReserveHeaderBytes: transportHeaderLength + extraHeaderReserveLength, Payload: buf, }) pkt.NetworkProtocolNumber = proto if _, err := rand.Read(pkt.TransportHeader().Push(transportHeaderLength)); err != nil { panic(fmt.Sprintf("rand.Read: %s", err)) } return pkt } func checkIGMPStats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) { t.Helper() if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != reports { t.Errorf("got s.Stats().IGMP.PacketsSent.V2MembershipReport.Value() = %d, want = %d", got, reports) } if got := s.Stats().IGMP.PacketsSent.V3MembershipReport.Value(); got != reportsV2 { t.Errorf("got s.Stats().IGMP.PacketsSent.V3MembershipReport.Value() = %d, want = %d", got, reportsV2) } if got := s.Stats().IGMP.PacketsSent.LeaveGroup.Value(); got != leaves { t.Errorf("got s.Stats().IGMP.PacketsSent.LeaveGroup.Value() = %d, want = %d", got, leaves) } } // CheckIGMPv2Stats checks IGMPv2 stats. func CheckIGMPv2Stats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) { t.Helper() // We still check V3 stats in V2 compatibility tests because the test may send // V3 reports before we drop into compatibility mode. checkIGMPStats(t, s, reports, leaves, reportsV2) } // CheckIGMPv3Stats checks IGMPv3 stats. func CheckIGMPv3Stats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) { t.Helper() // In IGMPv3 tests, reports/leaves are just IGMPv3 reports. checkIGMPStats(t, s, 0 /* reports */, 0 /* leaves */, reports+leaves+reportsV2) } func checkMLDStats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) { t.Helper() if got := s.Stats().ICMP.V6.PacketsSent.MulticastListenerReport.Value(); got != reports { t.Errorf("got s.Stats().ICMP.V6.PacketsSent.MulticastListenerReport.Value() = %d, want = %d", got, reports) } if got := s.Stats().ICMP.V6.PacketsSent.MulticastListenerReportV2.Value(); got != reportsV2 { t.Errorf("got s.Stats().ICMP.V6.PacketsSent.MulticastListenerReportV2.Value() = %d, want = %d", got, reportsV2) } if got := s.Stats().ICMP.V6.PacketsSent.MulticastListenerDone.Value(); got != leaves { t.Errorf("got s.Stats().ICMP.V6.PacketsSent.MulticastListenerDone.Value() = %d, want = %d", got, leaves) } } // CheckMLDv1Stats checks MLDv1 stats. func CheckMLDv1Stats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) { t.Helper() // We still check V2 stats in V1 compatibility tests because the test may send // V2 reports before we drop into compatibility mode. checkMLDStats(t, s, reports, leaves, reportsV2) } // CheckMLDv2Stats checks MLDv2 stats. func CheckMLDv2Stats(t *testing.T, s *stack.Stack, reports, leaves, reportsV2 uint64) { t.Helper() // In MLDv2 tests, reports/leaves are just MLDv2 reports. checkMLDStats(t, s, 0 /* reports */, 0 /* leaves */, reports+leaves+reportsV2) } // 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 *buffer.View, srcAddr tcpip.Address, expectedRecords map[tcpip.Address]header.IGMPv3ReportRecordType) { t.Helper() checker.IPv4(t, v, checker.SrcAddr(srcAddr), checker.DstAddr(header.IGMPv3RoutersAddress), checker.TTL(header.IGMPTTL), checker.IPv4RouterAlert(), checker.IGMPv3Report(expectedRecords), ) } // ValidateIGMPv3Report validates an IGMPv3 report. func ValidateIGMPv3Report(t *testing.T, v *buffer.View, srcAddr tcpip.Address, addrs []tcpip.Address, recordType header.IGMPv3ReportRecordType) { t.Helper() records := make(map[tcpip.Address]header.IGMPv3ReportRecordType) for _, addr := range addrs { 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 == nil { 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 == nil { 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 *buffer.View, srcAddr tcpip.Address, expectedRecords map[tcpip.Address]header.MLDv2ReportRecordType) { t.Helper() checker.IPv6WithExtHdr(t, v, checker.IPv6ExtHdr( checker.IPv6HopByHopExtensionHeader(checker.IPv6RouterAlert(header.IPv6RouterAlertMLD)), ), checker.SrcAddr(srcAddr), checker.DstAddr(header.MLDv2RoutersAddress), checker.TTL(header.MLDHopLimit), checker.MLDv2Report(expectedRecords), ) } // ValidateMLDv2Report validates an MLDv2 report. func ValidateMLDv2Report(t *testing.T, v *buffer.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 == nil { 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 == nil { 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) } }