Files
Kevin KrakauerandgVisor bot 3c75945fd6 netstack: remove PacketBuffer.IsNil()
The change was originally motivated by ticket references (cl/450976957), which
were never implemented.

PiperOrigin-RevId: 617480960
2024-03-20 05:31:48 -07:00

825 lines
28 KiB
Go

// 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 ipv6_test
import (
"math/rand"
"os"
"testing"
"time"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/refs"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checker"
"gvisor.dev/gvisor/pkg/tcpip/faketime"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
iptestutil "gvisor.dev/gvisor/pkg/tcpip/network/internal/testutil"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv6"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/testutil"
)
var (
linkLocalAddr = testutil.MustParse6("fe80::1")
globalAddr = testutil.MustParse6("a80::1")
globalMulticastAddr = testutil.MustParse6("ff05:100::2")
unusedMulticastAddr = testutil.MustParse6("ff05:100::3")
linkLocalAddrSNMC = header.SolicitedNodeAddr(linkLocalAddr)
globalAddrSNMC = header.SolicitedNodeAddr(globalAddr)
)
func checkVersion(t *testing.T, s *stack.Stack, nicID tcpip.NICID, v1 bool) {
if !v1 {
return
}
ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber)
if err != nil {
t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
}
mldEP, ok := ep.(ipv6.MLDEndpoint)
if !ok {
t.Fatalf("got (%T).(%T) = (_, false), want = (_ true)", ep, mldEP)
}
mldEP.SetMLDVersion(ipv6.MLDVersion1)
}
func validateMLDPacket(t *testing.T, v *buffer.View, localAddress, remoteAddress tcpip.Address, mldType header.ICMPv6Type, groupAddress tcpip.Address) {
t.Helper()
defer v.Release()
checker.IPv6WithExtHdr(t, v,
checker.IPv6ExtHdr(
checker.IPv6HopByHopExtensionHeader(checker.IPv6RouterAlert(header.IPv6RouterAlertMLD)),
),
checker.SrcAddr(localAddress),
checker.DstAddr(remoteAddress),
checker.TTL(header.MLDHopLimit),
checker.MLD(mldType, header.MLDMinimumSize,
checker.MLDMaxRespDelay(0),
checker.MLDMulticastAddress(groupAddress),
),
)
}
func validateMLDv2ReportPacket(t *testing.T, v *buffer.View, localAddress tcpip.Address, groupAddress tcpip.Address, recordType header.MLDv2ReportRecordType) {
t.Helper()
defer v.Release()
iptestutil.ValidateMLDv2Report(t, v, localAddress, []tcpip.Address{groupAddress}, recordType)
}
type mldTestContext struct {
s *stack.Stack
}
func (c *mldTestContext) cleanup() {
c.s.Close()
c.s.Wait()
}
func newMLDTestContext() mldTestContext {
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
MLD: ipv6.MLDOptions{
Enabled: true,
},
})},
})
return mldTestContext{s: s}
}
func TestIPv6JoinLeaveSolicitedNodeAddressPerformsMLD(t *testing.T) {
const nicID = 1
tests := []struct {
name string
v1Compatibility bool
validate func(t *testing.T, v *buffer.View, localAddress tcpip.Address, groupAddress tcpip.Address, leave bool)
}{
{
name: "V1 Compatibility",
v1Compatibility: true,
validate: func(t *testing.T, v *buffer.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)
},
},
{
name: "V2",
v1Compatibility: false,
validate: func(t *testing.T, v *buffer.View, localAddress tcpip.Address, groupAddress tcpip.Address, leave bool) {
t.Helper()
recordType := header.MLDv2ReportRecordChangeToExcludeMode
if leave {
recordType = header.MLDv2ReportRecordChangeToIncludeMode
}
validateMLDv2ReportPacket(t, v, localAddress, groupAddress, recordType)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := newMLDTestContext()
defer c.cleanup()
s := c.s
e := channel.New(1, header.IPv6MinimumMTU, "")
defer e.Close()
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
checkVersion(t, s, nicID, test.v1Compatibility)
// The stack will join an address's solicited node multicast address when
// an address is added. An MLD report message should be sent for the
// solicited-node group.
protocolAddr := tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: linkLocalAddr.WithPrefix(),
}
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
if p := e.Read(); p == nil {
t.Fatal("expected a report message to be sent")
} else {
test.validate(t, stack.PayloadSince(p.NetworkHeader()), linkLocalAddr, linkLocalAddrSNMC, false /* leave */)
p.DecRef()
}
// The stack will leave an address's solicited node multicast address when
// an address is removed. An MLD done message should be sent for the
// solicited-node group.
if err := s.RemoveAddress(nicID, linkLocalAddr); err != nil {
t.Fatalf("RemoveAddress(%d, %s) = %s", nicID, linkLocalAddr, err)
}
if p := e.Read(); p == nil {
t.Fatal("expected a done message to be sent")
} else {
test.validate(t, stack.PayloadSince(p.NetworkHeader()), header.IPv6Any, linkLocalAddrSNMC, true /* leave */)
p.DecRef()
}
})
}
}
func TestSendQueuedMLDReports(t *testing.T) {
const (
nicID = 1
maxReports = 2
)
tests := []struct {
name string
dadTransmits uint8
retransmitTimer time.Duration
}{
{
name: "DAD Disabled",
dadTransmits: 0,
retransmitTimer: 0,
},
{
name: "DAD Enabled",
dadTransmits: 1,
retransmitTimer: ipv6.UnsolicitedReportIntervalMax + time.Second,
},
}
subTests := []struct {
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: iptestutil.ValidMultipleMLDv1ReportLeaves,
checkStats: iptestutil.CheckMLDv1Stats,
},
{
name: "V2",
v1Compatibility: false,
validate: func(t *testing.T, e *channel.Endpoint, localAddress tcpip.Address, groupAddresses []tcpip.Address, leave bool) {
t.Helper()
recordType := header.MLDv2ReportRecordChangeToExcludeMode
if leave {
recordType = header.MLDv2ReportRecordChangeToIncludeMode
}
iptestutil.ValidateMLDv2RecordsAcrossReports(t, e, localAddress, groupAddresses, recordType)
},
checkStats: iptestutil.CheckMLDv2Stats,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
for _, subTest := range subTests {
t.Run(subTest.name, func(t *testing.T) {
dadResolutionTime := test.retransmitTimer * time.Duration(test.dadTransmits)
clock := faketime.NewManualClock()
s := stack.New(stack.Options{
RandSource: rand.NewSource(time.Now().UnixNano()),
NetworkProtocols: []stack.NetworkProtocolFactory{ipv6.NewProtocolWithOptions(ipv6.Options{
DADConfigs: stack.DADConfigurations{
DupAddrDetectTransmits: test.dadTransmits,
RetransmitTimer: test.retransmitTimer,
},
MLD: ipv6.MLDOptions{
Enabled: true,
},
})},
Clock: clock,
})
// Allow space for an extra packet so we can observe packets that were
// unexpectedly sent.
e := channel.New(maxReports+int(test.dadTransmits)+1 /* extra */, header.IPv6MinimumMTU, "")
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
defer func() {
s.Close()
s.Wait()
e.Close()
}()
resolveDAD := func(addr, snmc tcpip.Address) {
t.Helper()
clock.Advance(dadResolutionTime)
if p := e.Read(); p == nil {
t.Fatal("expected DAD packet")
} else {
payload := stack.PayloadSince(p.NetworkHeader())
defer payload.Release()
checker.IPv6(t, payload,
checker.SrcAddr(header.IPv6Any),
checker.DstAddr(snmc),
checker.TTL(header.NDPHopLimit),
checker.NDPNS(
checker.NDPNSTargetAddress(addr),
))
p.DecRef()
}
}
checkVersion(t, s, nicID, subTest.v1Compatibility)
var reportCounter uint64
var doneCounter uint64
var reportV2Counter uint64
subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter)
// Joining a group without an assigned address should send an MLD report
// with the unspecified address.
if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, globalMulticastAddr); err != nil {
t.Fatalf("JoinGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, globalMulticastAddr, err)
}
reportCounter++
subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter)
subTest.validate(t, e, header.IPv6Any, []tcpip.Address{globalMulticastAddr}, false /* leave */)
clock.Advance(time.Hour)
if p := e.Read(); p != nil {
t.Errorf("got unexpected packet = %#v", p)
p.DecRef()
}
if t.Failed() {
t.FailNow()
}
// Adding a global address should not send reports for the already joined
// group since we should only send queued reports when a link-local
// address is assigned.
//
// Note, we will still expect to send a report for the global address's
// solicited node address from the unspecified address as per RFC 3590
// section 4.
properties := stack.AddressProperties{PEB: stack.FirstPrimaryEndpoint}
globalProtocolAddr := tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: globalAddr.WithPrefix(),
}
if err := s.AddProtocolAddress(nicID, globalProtocolAddr, properties); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, %+v): %s", nicID, globalProtocolAddr, properties, err)
}
reportCounter++
subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter)
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)
subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter)
}
// Leave the group since we don't care about the global address's
// solicited node multicast group membership.
if err := s.LeaveGroup(ipv6.ProtocolNumber, nicID, globalAddrSNMC); err != nil {
t.Fatalf("LeaveGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, globalAddrSNMC, err)
}
if !subTest.v1Compatibility {
doneCounter++
subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter)
subTest.validate(t, e, header.IPv6Any, []tcpip.Address{globalAddrSNMC}, true /* leave */)
}
subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter)
if p := e.Read(); p != nil {
t.Errorf("got unexpected packet = %#v", p)
p.DecRef()
}
if t.Failed() {
t.FailNow()
}
// Adding a link-local address should send a report for its solicited node
// address and globalMulticastAddr.
linkLocalProtocolAddr := tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: linkLocalAddr.WithPrefix(),
}
if err := s.AddProtocolAddress(nicID, linkLocalProtocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, linkLocalProtocolAddr, err)
}
if dadResolutionTime != 0 {
reportCounter++
subTest.validate(t, e, header.IPv6Any, []tcpip.Address{linkLocalAddrSNMC}, false /* leave */)
resolveDAD(linkLocalAddr, linkLocalAddrSNMC)
}
// We expect two batches of reports to be sent (1 batch when the
// link-local address is assigned, and another after the maximum
// unsolicited report interval.
for i := 0; i < 2; i++ {
// MLDv1 always sends a single message per group.
//
// MLDv2 sends a single message per group when we first get an
// IPv6 link-local address assigned, but later reports (sent by
// the state changed timer) coalesce records for groups.
if subTest.v1Compatibility || i == 0 {
reportCounter += maxReports
} else {
reportCounter++
}
subTest.checkStats(t, s, reportCounter, doneCounter, reportV2Counter)
subTest.validate(
t,
e,
linkLocalAddr,
[]tcpip.Address{globalMulticastAddr, linkLocalAddrSNMC},
false, /* leave */
)
clock.Advance(ipv6.UnsolicitedReportIntervalMax)
}
// Should not send any more reports.
clock.Advance(time.Hour)
if p := e.Read(); p != nil {
t.Errorf("got unexpected packet = %#v", p)
p.DecRef()
}
})
}
})
}
}
// createAndInjectMLDPacket creates and injects an MLD packet with the
// specified fields.
func createAndInjectMLDPacket(e *channel.Endpoint, mldType header.ICMPv6Type, hopLimit uint8, srcAddress, groupAddress tcpip.Address, withRouterAlertOption bool, routerAlertValue header.IPv6RouterAlertValue) {
var extensionHeaders header.IPv6ExtHdrSerializer
if withRouterAlertOption {
extensionHeaders = header.IPv6ExtHdrSerializer{
header.IPv6SerializableHopByHopExtHdr{
&header.IPv6RouterAlertOption{Value: routerAlertValue},
},
}
}
extensionHeadersLength := extensionHeaders.Length()
payloadLength := extensionHeadersLength + header.ICMPv6HeaderSize + header.MLDMinimumSize
buf := make([]byte, header.IPv6MinimumSize+payloadLength)
ip := header.IPv6(buf)
ip.Encode(&header.IPv6Fields{
PayloadLength: uint16(payloadLength),
HopLimit: hopLimit,
TransportProtocol: header.ICMPv6ProtocolNumber,
SrcAddr: srcAddress,
DstAddr: header.IPv6AllNodesMulticastAddress,
ExtensionHeaders: extensionHeaders,
})
icmp := header.ICMPv6(ip.Payload()[extensionHeadersLength:])
icmp.SetType(mldType)
mld := header.MLD(icmp.MessageBody())
mld.SetMaximumResponseDelay(0)
mld.SetMulticastAddress(groupAddress)
icmp.SetChecksum(header.ICMPv6Checksum(header.ICMPv6ChecksumParams{
Header: icmp,
Src: srcAddress,
Dst: header.IPv6AllNodesMulticastAddress,
}))
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
Payload: buffer.MakeWithData(buf),
})
e.InjectInbound(ipv6.ProtocolNumber, pkt)
pkt.DecRef()
}
func TestMLDPacketValidation(t *testing.T) {
const nicID = 1
linkLocalAddr2 := testutil.MustParse6("fe80::2")
tests := []struct {
name string
messageType header.ICMPv6Type
srcAddr tcpip.Address
includeRouterAlertOption bool
routerAlertValue header.IPv6RouterAlertValue
hopLimit uint8
expectValidMLD bool
getMessageTypeStatValue func(tcpip.Stats) uint64
}{
{
name: "valid",
messageType: header.ICMPv6MulticastListenerQuery,
includeRouterAlertOption: true,
routerAlertValue: header.IPv6RouterAlertMLD,
srcAddr: linkLocalAddr2,
hopLimit: header.MLDHopLimit,
expectValidMLD: true,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerQuery.Value() },
},
{
name: "bad hop limit",
messageType: header.ICMPv6MulticastListenerReport,
includeRouterAlertOption: true,
routerAlertValue: header.IPv6RouterAlertMLD,
srcAddr: linkLocalAddr2,
hopLimit: header.MLDHopLimit + 1,
expectValidMLD: false,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerReport.Value() },
},
{
name: "src ip not link local",
messageType: header.ICMPv6MulticastListenerReport,
includeRouterAlertOption: true,
routerAlertValue: header.IPv6RouterAlertMLD,
srcAddr: globalAddr,
hopLimit: header.MLDHopLimit,
expectValidMLD: false,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerReport.Value() },
},
{
name: "missing router alert ip option",
messageType: header.ICMPv6MulticastListenerDone,
includeRouterAlertOption: false,
srcAddr: linkLocalAddr2,
hopLimit: header.MLDHopLimit,
expectValidMLD: false,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerDone.Value() },
},
{
name: "incorrect router alert value",
messageType: header.ICMPv6MulticastListenerDone,
includeRouterAlertOption: true,
routerAlertValue: header.IPv6RouterAlertRSVP,
srcAddr: linkLocalAddr2,
hopLimit: header.MLDHopLimit,
expectValidMLD: false,
getMessageTypeStatValue: func(stats tcpip.Stats) uint64 { return stats.ICMP.V6.PacketsReceived.MulticastListenerDone.Value() },
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
c := newMLDTestContext()
defer c.cleanup()
s := c.s
e := channel.New(1, header.IPv6MinimumMTU, "")
defer e.Close()
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
stats := s.Stats()
// Verify that every relevant stats is zero'd before we send a packet.
if got := test.getMessageTypeStatValue(s.Stats()); got != 0 {
t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 0", got)
}
if got := stats.ICMP.V6.PacketsReceived.Invalid.Value(); got != 0 {
t.Errorf("got stats.ICMP.V6.PacketsReceived.Invalid.Value() = %d, want = 0", got)
}
if got := stats.IP.PacketsDelivered.Value(); got != 0 {
t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 0", got)
}
createAndInjectMLDPacket(e, test.messageType, test.hopLimit, test.srcAddr, header.IPv6Any, test.includeRouterAlertOption, test.routerAlertValue)
// We always expect the packet to pass IP validation.
if got := stats.IP.PacketsDelivered.Value(); got != 1 {
t.Fatalf("got stats.IP.PacketsDelivered.Value() = %d, want = 1", got)
}
// Even when the MLD-specific validation checks fail, we expect the
// corresponding MLD counter to be incremented.
if got := test.getMessageTypeStatValue(s.Stats()); got != 1 {
t.Errorf("got test.getMessageTypeStatValue(s.Stats()) = %d, want = 1", got)
}
var expectedInvalidCount uint64
if !test.expectValidMLD {
expectedInvalidCount = 1
}
if got := stats.ICMP.V6.PacketsReceived.Invalid.Value(); got != expectedInvalidCount {
t.Errorf("got stats.ICMP.V6.PacketsReceived.Invalid.Value() = %d, want = %d", got, expectedInvalidCount)
}
})
}
}
func TestMLDSkipProtocol(t *testing.T) {
const nicID = 1
tests := []struct {
name string
group string
expectReport bool
}{
{
name: "Reserved0",
group: "\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: false,
},
{
name: "Interface Local",
group: "\xff\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: false,
},
{
name: "Link Local",
group: "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Realm Local",
group: "\xff\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Admin Local",
group: "\xff\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Site Local",
group: "\xff\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(6)",
group: "\xff\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(7)",
group: "\xff\x07\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Organization Local",
group: "\xff\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(9)",
group: "\xff\x09\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(A)",
group: "\xff\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(B)",
group: "\xff\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(C)",
group: "\xff\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Unassigned(D)",
group: "\xff\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "Global",
group: "\xff\x0e\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
{
name: "ReservedF",
group: "\xff\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11",
expectReport: true,
},
}
subTests := []struct {
name string
v1Compatibility bool
validate func(t *testing.T, v *buffer.View, localAddress tcpip.Address, groupAddress tcpip.Address)
}{
{
name: "V1 Compatibility",
v1Compatibility: true,
validate: func(t *testing.T, v *buffer.View, localAddress tcpip.Address, groupAddress tcpip.Address) {
t.Helper()
validateMLDPacket(t, v, localAddress, groupAddress, header.ICMPv6MulticastListenerReport, groupAddress)
},
},
{
name: "V2",
v1Compatibility: false,
validate: func(t *testing.T, v *buffer.View, localAddress tcpip.Address, groupAddress tcpip.Address) {
t.Helper()
validateMLDv2ReportPacket(t, v, localAddress, groupAddress, header.MLDv2ReportRecordChangeToExcludeMode)
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
for _, subTest := range subTests {
t.Run(subTest.name, func(t *testing.T) {
c := newMLDTestContext()
s := c.s
e := channel.New(1, header.IPv6MinimumMTU, "")
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
defer e.Close()
defer c.cleanup()
checkVersion(t, s, nicID, subTest.v1Compatibility)
protocolAddr := tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: linkLocalAddr.WithPrefix(),
}
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
if p := e.Read(); p == nil {
t.Fatal("expected a report message to be sent")
} else {
subTest.validate(t, stack.PayloadSince(p.NetworkHeader()), linkLocalAddr, linkLocalAddrSNMC)
p.DecRef()
}
testGroup := tcpip.AddrFromSlice([]byte(test.group))
if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, testGroup); err != nil {
t.Fatalf("s.JoinGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, testGroup, err)
}
if isInGroup, err := s.IsInGroup(nicID, testGroup); err != nil {
t.Fatalf("IsInGroup(%d, %s): %s", nicID, testGroup, err)
} else if !isInGroup {
t.Fatalf("got IsInGroup(%d, %s) = false, want = true", nicID, testGroup)
}
if !test.expectReport {
if p := e.Read(); p != nil {
t.Fatalf("got e.Read() = (%#v, true), want = (_, false)", p)
}
return
}
if p := e.Read(); p == nil {
t.Fatal("expected a report message to be sent")
} else {
subTest.validate(t, stack.PayloadSince(p.NetworkHeader()), linkLocalAddr, testGroup)
p.DecRef()
}
})
}
})
}
}
func TestGetSetMLDVersion(t *testing.T) {
const nicID = 1
c := newMLDTestContext()
s := c.s
e := channel.New(1, header.IPv6MinimumMTU, "")
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
defer e.Close()
defer c.cleanup()
ep, err := s.GetNetworkEndpoint(nicID, header.IPv6ProtocolNumber)
if err != nil {
t.Fatalf("s.GetNetworkEndpoint(%d, %d): %s", nicID, header.IPv6ProtocolNumber, err)
}
mldEP, ok := ep.(ipv6.MLDEndpoint)
if !ok {
t.Fatalf("got (%T).(%T) = (_, false), want = (_ true)", ep, mldEP)
}
if got := mldEP.GetMLDVersion(); got != ipv6.MLDVersion2 {
t.Errorf("got mldEP.GetMLDVersion() = %d, want = %d", got, ipv6.MLDVersion2)
}
protocolAddr := tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: linkLocalAddr.WithPrefix(),
}
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
if p := e.Read(); p == nil {
t.Fatal("expected a report message to be sent")
} else {
validateMLDv2ReportPacket(t, stack.PayloadSince(p.NetworkHeader()), linkLocalAddr, linkLocalAddrSNMC, header.MLDv2ReportRecordChangeToExcludeMode)
p.DecRef()
}
if got := mldEP.SetMLDVersion(ipv6.MLDVersion1); got != ipv6.MLDVersion2 {
t.Errorf("got mldEP.SetMLDVersion(%d) = %d, want = %d", ipv6.MLDVersion1, got, ipv6.MLDVersion2)
}
if got := mldEP.GetMLDVersion(); got != ipv6.MLDVersion1 {
t.Errorf("got mldEP.GetMLDVersion() = %d, want = %d", got, ipv6.MLDVersion1)
}
if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, globalMulticastAddr); err != nil {
t.Fatalf("s.JoinGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, globalMulticastAddr, err)
}
if p := e.Read(); p == nil {
t.Fatal("expected a report message to be sent")
} else {
validateMLDPacket(t, stack.PayloadSince(p.NetworkHeader()), linkLocalAddr, globalMulticastAddr, header.ICMPv6MulticastListenerReport, globalMulticastAddr)
p.DecRef()
}
if got := mldEP.SetMLDVersion(ipv6.MLDVersion2); got != ipv6.MLDVersion1 {
t.Errorf("got mldEP.SetMLDVersion(%d) = %d, want = %d", ipv6.MLDVersion2, got, ipv6.MLDVersion1)
}
if got := mldEP.GetMLDVersion(); got != ipv6.MLDVersion2 {
t.Errorf("got mldEP.GetMLDVersion() = %d, want = %d", got, ipv6.MLDVersion2)
}
if err := s.LeaveGroup(ipv6.ProtocolNumber, nicID, globalMulticastAddr); err != nil {
t.Fatalf("s.LeaveGroup(%d, %d, %s): %s", ipv6.ProtocolNumber, nicID, globalMulticastAddr, err)
}
if p := e.Read(); p == nil {
t.Fatal("expected a report message to be sent")
} else {
validateMLDv2ReportPacket(t, stack.PayloadSince(p.NetworkHeader()), linkLocalAddr, globalMulticastAddr, header.MLDv2ReportRecordChangeToIncludeMode)
p.DecRef()
}
}
func TestMain(m *testing.M) {
refs.SetLeakMode(refs.LeaksPanic)
code := m.Run()
refs.DoLeakCheck()
os.Exit(code)
}