Perform MLDv2/IGMPv3 without SSM

This change introduces support for MLDv2/IGMPv3. Note that this change
does not yet introduce APIs to perform source filtering so SSM is not
yet supported. Also note that this change does not yet coalesce records
in a report as that will come in a follow-up.

Updates #8346

PiperOrigin-RevId: 501336347
This commit is contained in:
Ghanan Gowripalan
2023-01-11 11:39:25 -08:00
committed by gVisor bot
parent 5bb418ecdb
commit 4632d45dd8
26 changed files with 4408 additions and 1382 deletions
+129
View File
@@ -1241,6 +1241,69 @@ func MLDMulticastAddress(want tcpip.Address) TransportChecker {
}
}
// MLDv2Report creates a checker that checks that the packet contains a valid
// MLDv2 report with the specified records.
func MLDv2Report(expectedReport header.MLDv2ReportSerializer) NetworkChecker {
return func(t *testing.T, h []header.Network) {
t.Helper()
// Check normal ICMPv6 first.
ICMPv6(
ICMPv6Type(header.ICMPv6MulticastListenerV2Report),
ICMPv6Code(0))(t, h)
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)
}
if got, want := record.RecordType(), expectedRecords[0].RecordType; got != want {
t.Errorf("got record.RecordType() = %d, want = %d", got, want)
}
if got := record.AuxDataLen(); got != 0 {
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:]
}
expectedRecords = expectedRecords[1:]
}
if record, res := records.Next(); res != header.MLDv2ReportMulticastAddressRecordIteratorNextDone {
t.Fatalf("got records.Next() = (%#v, %d), want = (_, %d)", record, res, header.MLDv2ReportMulticastAddressRecordIteratorNextDone)
}
}
}
// NDP creates a checker that checks that the packet contains a valid NDP
// message for type of ty, with potentially additional checks specified by
// checkers.
@@ -1532,6 +1595,72 @@ func IGMPGroupAddress(want tcpip.Address) TransportChecker {
}
}
// IGMPv3Report creates a checker that checks that the packet contains a valid
// IGMPv3 report with the specified records.
func IGMPv3Report(expectedReport header.IGMPv3ReportSerializer) NetworkChecker {
return func(t *testing.T, h []header.Network) {
t.Helper()
last := h[len(h)-1]
if p := last.TransportProtocol(); p != header.IGMPProtocolNumber {
t.Fatalf("Bad protocol, got %d, want %d", p, header.IGMPProtocolNumber)
}
igmp := header.IGMP(last.Payload())
if got := igmp.Type(); got != header.IGMPv3MembershipReport {
t.Errorf("got igmp.Type() = %d, want = %d", got, header.IGMPv3MembershipReport)
}
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)
}
if got, want := record.RecordType(), expectedRecords[0].RecordType; got != want {
t.Errorf("got record.RecordType() = %d, want = %d", got, want)
}
if got := record.AuxDataLen(); got != 0 {
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:]
}
expectedRecords = expectedRecords[1:]
}
if record, res := records.Next(); res != header.IGMPv3ReportGroupAddressRecordIteratorNextDone {
t.Fatalf("got records.Next() = (%#v, %d), want = (_, %d)", record, res, header.IGMPv3ReportGroupAddressRecordIteratorNextDone)
}
}
}
// IPv6ExtHdrChecker is a function to check an extension header.
type IPv6ExtHdrChecker func(*testing.T, header.IPv6PayloadHeader)
+4
View File
@@ -121,6 +121,10 @@ const (
ICMPv6MulticastListenerQuery ICMPv6Type = 130
ICMPv6MulticastListenerReport ICMPv6Type = 131
ICMPv6MulticastListenerDone ICMPv6Type = 132
// Multicast Listener Discovert Version 2 (MLDv2) messages, see RFC 3810.
ICMPv6MulticastListenerV2Report ICMPv6Type = 143
)
// IsErrorType returns true if the receiver is an ICMP error type.
+2 -2
View File
@@ -114,7 +114,7 @@ func (b IGMP) MaxRespTime() time.Duration {
// messages, and specifies the maximum allowed time before sending a
// responding report in units of 1/10 second. In all other messages, it
// is set to zero by the sender and ignored by receivers.
return DecisecondToDuration(b[igmpMaxRespTimeOffset])
return DecisecondToDuration(uint16(b[igmpMaxRespTimeOffset]))
}
// SetMaxRespTime sets the MaxRespTimeField.
@@ -179,6 +179,6 @@ func IGMPCalculateChecksum(h IGMP) uint16 {
// DecisecondToDuration converts a value representing deci-seconds to a
// time.Duration.
func DecisecondToDuration(ds uint8) time.Duration {
func DecisecondToDuration(ds uint16) time.Duration {
return time.Duration(ds) * time.Second / 10
}
+1 -1
View File
@@ -65,7 +65,7 @@ func TestIGMPHeader(t *testing.T) {
respTime := byte(0x02)
igmpHeader.SetMaxRespTime(respTime)
if got, want := igmpHeader.MaxRespTime(), header.DecisecondToDuration(respTime); got != want {
if got, want := igmpHeader.MaxRespTime(), header.DecisecondToDuration(uint16(respTime)); got != want {
t.Errorf("got igmpHeader.MaxRespTime() = %s, want = %s", got, want)
}
+48
View File
@@ -24,6 +24,14 @@ import (
)
const (
// IGMPv3RoutersAddress is the address to send IGMPv3 reports to.
//
// As per RFC 3376 section 4.2.14,
//
// Version 3 Reports are sent with an IP destination address of
// 224.0.0.22, to which all IGMPv3-capable multicast routers listen.
IGMPv3RoutersAddress tcpip.Address = "\xe0\x00\x00\x16"
// IGMPv3QueryMinimumSize is the mimum size of a valid IGMPv3 query,
// as per RFC 3376 section 4.1.
IGMPv3QueryMinimumSize = 12
@@ -66,6 +74,46 @@ func (i IGMPv3Query) MaximumResponseCode() uint8 {
return i[igmpv3QueryMaxRespCodeOffset]
}
// IGMPv3MaximumResponseDelay returns the Maximum Response Delay in an IGMPv3
// Maximum Response Code.
//
// As per RFC 3376 section 4.1.1,
//
// The Max Resp Code field specifies the maximum time allowed before
// sending a responding report. The actual time allowed, called the Max
// Resp Time, is represented in units of 1/10 second and is derived from
// the Max Resp Code as follows:
//
// If Max Resp Code < 128, Max Resp Time = Max Resp Code
//
// If Max Resp Code >= 128, Max Resp Code represents a floating-point
// value as follows:
//
// 0 1 2 3 4 5 6 7
// +-+-+-+-+-+-+-+-+
// |1| exp | mant |
// +-+-+-+-+-+-+-+-+
//
// Max Resp Time = (mant | 0x10) << (exp + 3)
//
// Small values of Max Resp Time allow IGMPv3 routers to tune the "leave
// latency" (the time between the moment the last host leaves a group
// and the moment the routing protocol is notified that there are no
// more members). Larger values, especially in the exponential range,
// allow tuning of the burstiness of IGMP traffic on a network.
func IGMPv3MaximumResponseDelay(codeRaw uint8) time.Duration {
code := uint16(codeRaw)
if code < 128 {
return DecisecondToDuration(code)
}
const mantBits = 4
const expMask = 0b111
exp := (code >> mantBits) & expMask
mant := code & ((1 << mantBits) - 1)
return DecisecondToDuration((mant | 0x10) << (exp + 3))
}
// GroupAddress returns the group address.
func (i IGMPv3Query) GroupAddress() tcpip.Address {
return tcpip.Address(i[igmpv3QueryGroupAddressOffset:][:IPv4AddressSize])
+13 -1
View File
@@ -24,6 +24,16 @@ import (
)
const (
// MLDv2RoutersAddress is the address to send MLDv2 reports to.
//
// As per RFC 3810 section 5.2.14,
//
// Version 2 Multicast Listener Reports are sent with an IP destination
// address of FF02:0:0:0:0:0:0:16, to which all MLDv2-capable multicast
// routers listen (see section 11 for IANA considerations related to
// this special destination address).
MLDv2RoutersAddress tcpip.Address = "\xff\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x16"
// MLDv2QueryMinimumSize is the minimum size for an MLDv2 message.
MLDv2QueryMinimumSize = 24
@@ -35,6 +45,9 @@ const (
// field within MLDv2Query.
mldv2QueryNumberOfSourcesOffset = 22
// MLDv2ReportMinimumSize is the minimum size of an MLDv2 report.
MLDv2ReportMinimumSize = 24
// mldv2QuerySourcesOffset is the offset to the Sources field within
// MLDv2Query.
mldv2QuerySourcesOffset = 24
@@ -140,7 +153,6 @@ func MLDv2MaximumResponseDelay(codeRaw uint16) time.Duration {
exp := (code >> mantBits) & expMask
mant := code & ((1 << mantBits) - 1)
return (mant | 0x1000) << (exp + 3) * time.Millisecond
}
// MulticastAddress returns the Multicast Address.
+6 -1
View File
@@ -82,6 +82,11 @@ func mldv2AndIGMPv3QuerierQueryCodeToInterval(code uint8) time.Duration {
return (mant | 0x10) << (exp + 3) * time.Second
}
// MakeAddressIterator returns an AddressIterator.
func MakeAddressIterator(addressSize int, buf *bytes.Buffer) AddressIterator {
return AddressIterator{addressSize: addressSize, buf: buf}
}
// AddressIterator is an iterator over IPv6 addresses.
type AddressIterator struct {
addressSize int
@@ -115,5 +120,5 @@ func makeAddressIterator(b []byte, expectedAddresses uint16, addressSize int) (A
if len(b) < expectedLen {
return AddressIterator{}, false
}
return AddressIterator{addressSize: addressSize, buf: bytes.NewBuffer(b[:expectedLen])}, true
return MakeAddressIterator(addressSize, bytes.NewBuffer(b[:expectedLen])), true
}
+5 -8
View File
@@ -215,18 +215,15 @@ func ICMPv6(pkt stack.PacketBufferPtr) bool {
header.ICMPv6RouterAdvert,
header.ICMPv6NeighborSolicit,
header.ICMPv6NeighborAdvert,
header.ICMPv6RedirectMsg:
header.ICMPv6RedirectMsg,
header.ICMPv6MulticastListenerQuery,
header.ICMPv6MulticastListenerReport,
header.ICMPv6MulticastListenerV2Report,
header.ICMPv6MulticastListenerDone:
size := pkt.Data().Size()
if _, ok := pkt.TransportHeader().Consume(size); !ok {
panic(fmt.Sprintf("expected to consume the full data of size = %d bytes into transport header", size))
}
case header.ICMPv6MulticastListenerQuery,
header.ICMPv6MulticastListenerReport,
header.ICMPv6MulticastListenerDone:
size := header.ICMPv6HeaderSize + header.MLDMinimumSize
if _, ok := pkt.TransportHeader().Consume(size); !ok {
return false
}
case header.ICMPv6DstUnreachable,
header.ICMPv6PacketTooBig,
header.ICMPv6TimeExceeded,
+1
View File
@@ -21,6 +21,7 @@ go_test(
"//pkg/tcpip/header",
"//pkg/tcpip/link/channel",
"//pkg/tcpip/link/loopback",
"//pkg/tcpip/network/internal/testutil",
"//pkg/tcpip/network/ipv4",
"//pkg/tcpip/network/ipv6",
"//pkg/tcpip/prependable",
+2
View File
@@ -18,6 +18,7 @@ go_library(
deps = [
"//pkg/sync",
"//pkg/tcpip",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
],
)
@@ -34,6 +35,7 @@ go_test(
"//pkg/sync",
"//pkg/tcpip",
"//pkg/tcpip/faketime",
"//pkg/tcpip/header",
"//pkg/tcpip/stack",
"@com_github_google_go_cmp//cmp:go_default_library",
],
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -6,6 +6,7 @@ go_library(
name = "testutil",
srcs = ["testutil.go"],
visibility = [
"//pkg/tcpip/network:__pkg__",
"//pkg/tcpip/network/arp:__pkg__",
"//pkg/tcpip/network/internal/fragmentation:__pkg__",
"//pkg/tcpip/network/ipv4:__pkg__",
@@ -19,6 +19,7 @@ package testutil
import (
"fmt"
"math/rand"
"testing"
"gvisor.dev/gvisor/pkg/bufferv2"
"gvisor.dev/gvisor/pkg/tcpip"
@@ -124,3 +125,61 @@ func MakeRandPkt(transportHeaderLength int, extraHeaderReserveLength int, viewSi
}
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)
}
+158 -16
View File
@@ -16,6 +16,7 @@ package ipv4
import (
"fmt"
"math"
"time"
"gvisor.dev/gvisor/pkg/atomicbitops"
@@ -136,6 +137,104 @@ func (igmp *igmpState) ShouldPerformProtocol(groupAddress tcpip.Address) bool {
return groupAddress != header.IPv4AllSystems
}
type igmpv3ReportBuilder struct {
igmp *igmpState
records []header.IGMPv3ReportGroupAddressRecordSerializer
}
// AddRecord implements ip.MulticastGroupProtocolV2ReportBuilder.
func (b *igmpv3ReportBuilder) AddRecord(genericRecordType ip.MulticastGroupProtocolV2ReportRecordType, groupAddress tcpip.Address) {
var recordType header.IGMPv3ReportRecordType
switch genericRecordType {
case ip.MulticastGroupProtocolV2ReportRecordModeIsInclude:
recordType = header.IGMPv3ReportRecordModeIsInclude
case ip.MulticastGroupProtocolV2ReportRecordModeIsExclude:
recordType = header.IGMPv3ReportRecordModeIsExclude
case ip.MulticastGroupProtocolV2ReportRecordChangeToIncludeMode:
recordType = header.IGMPv3ReportRecordChangeToIncludeMode
case ip.MulticastGroupProtocolV2ReportRecordChangeToExcludeMode:
recordType = header.IGMPv3ReportRecordChangeToExcludeMode
case ip.MulticastGroupProtocolV2ReportRecordAllowNewSources:
recordType = header.IGMPv3ReportRecordAllowNewSources
case ip.MulticastGroupProtocolV2ReportRecordBlockOldSources:
recordType = header.IGMPv3ReportRecordBlockOldSources
default:
panic(fmt.Sprintf("unrecognied genericRecordType = %d", genericRecordType))
}
b.records = append(b.records, header.IGMPv3ReportGroupAddressRecordSerializer{
RecordType: recordType,
GroupAddress: groupAddress,
Sources: nil,
})
}
// Send implements ip.MulticastGroupProtocolV2ReportBuilder.
//
// +checklocksread:b.igmp.ep.mu
func (b *igmpv3ReportBuilder) Send() (sent bool, err tcpip.Error) {
options := header.IPv4OptionsSerializer{
&header.IPv4SerializableRouterAlertOption{},
}
mtu := int(b.igmp.ep.MTU()) - int(options.Length())
allSentWithSpecifiedAddress := true
var firstErr tcpip.Error
for records := b.records; len(records) != 0; {
spaceLeft := mtu
maxRecords := 0
for ; maxRecords < len(records); maxRecords++ {
tmp := spaceLeft - records[maxRecords].Length()
if tmp > 0 {
spaceLeft = tmp
} else {
break
}
}
serializer := header.IGMPv3ReportSerializer{Records: records[:maxRecords]}
records = records[maxRecords:]
icmpView := bufferv2.NewViewSize(serializer.Length())
serializer.SerializeInto(icmpView.AsSlice())
if sentWithSpecifiedAddress, err := b.igmp.writePacketInner(
icmpView,
b.igmp.ep.stats.igmp.packetsSent.v3MembershipReport,
options,
header.IGMPv3RoutersAddress,
); err != nil {
if firstErr != nil {
firstErr = nil
}
allSentWithSpecifiedAddress = false
} else if !sentWithSpecifiedAddress {
allSentWithSpecifiedAddress = false
}
}
return allSentWithSpecifiedAddress, firstErr
}
// NewReportV2Builder implements ip.MulticastGroupProtocol.
func (igmp *igmpState) NewReportV2Builder() ip.MulticastGroupProtocolV2ReportBuilder {
return &igmpv3ReportBuilder{igmp: igmp}
}
// V2QueryMaxRespCodeToV2Delay implements ip.MulticastGroupProtocol.
func (*igmpState) V2QueryMaxRespCodeToV2Delay(code uint16) time.Duration {
if code > math.MaxUint8 {
panic(fmt.Sprintf("got IGMPv3 MaxRespCode = %d, want <= %d", code, math.MaxUint8))
}
return header.IGMPv3MaximumResponseDelay(uint8(code))
}
// V2QueryMaxRespCodeToV1Delay implements ip.MulticastGroupProtocol.
func (*igmpState) V2QueryMaxRespCodeToV1Delay(code uint16) time.Duration {
return time.Duration(code) * time.Millisecond
}
// init sets up an igmpState struct, and is required to be called before using
// a new igmpState.
//
@@ -206,12 +305,16 @@ func (igmp *igmpState) isPacketValidLocked(pkt stack.PacketBufferPtr, messageTyp
// +checklocks:igmp.ep.mu
func (igmp *igmpState) handleIGMP(pkt stack.PacketBufferPtr, hasRouterAlertOption bool) {
received := igmp.ep.stats.igmp.packetsReceived
hdr, ok := pkt.Data().PullUp(header.IGMPMinimumSize)
hdr, ok := pkt.Data().PullUp(pkt.Data().Size())
if !ok {
received.invalid.Increment()
return
}
h := header.IGMP(hdr)
if len(h) < header.IGMPMinimumSize {
received.invalid.Increment()
return
}
// As per RFC 1071 section 1.3,
//
@@ -231,7 +334,14 @@ func (igmp *igmpState) handleIGMP(pkt stack.PacketBufferPtr, hasRouterAlertOptio
switch h.Type() {
case header.IGMPMembershipQuery:
received.membershipQuery.Increment()
if !isValid(header.IGMPQueryMinimumSize) {
if len(h) >= header.IGMPv3QueryMinimumSize {
if isValid(header.IGMPv3QueryMinimumSize) {
igmp.handleMembershipQueryV3(header.IGMPv3Query(h))
} else {
received.invalid.Increment()
}
return
} else if !isValid(header.IGMPQueryMinimumSize) {
received.invalid.Increment()
return
}
@@ -301,6 +411,24 @@ func (igmp *igmpState) handleMembershipQuery(groupAddress tcpip.Address, maxResp
igmp.genericMulticastProtocol.HandleQueryLocked(groupAddress, maxRespTime)
}
// handleMembershipQueryV3 handles a membership query.
//
// +checklocks:igmp.ep.mu
func (igmp *igmpState) handleMembershipQueryV3(igmpHdr header.IGMPv3Query) {
sources, ok := igmpHdr.Sources()
if !ok {
return
}
igmp.genericMulticastProtocol.HandleQueryV2Locked(
igmpHdr.GroupAddress(),
uint16(igmpHdr.MaximumResponseCode()),
sources,
igmpHdr.QuerierRobustnessVariable(),
igmpHdr.QuerierQueryInterval(),
)
}
// handleMembershipReport handles a membership report.
//
// +checklocks:igmp.ep.mu
@@ -318,9 +446,34 @@ func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip
igmpData.SetGroupAddress(groupAddress)
igmpData.SetChecksum(header.IGMPCalculateChecksum(igmpData))
var reportType tcpip.MultiCounterStat
sentStats := igmp.ep.stats.igmp.packetsSent
switch igmpType {
case header.IGMPv1MembershipReport:
reportType = sentStats.v1MembershipReport
case header.IGMPv2MembershipReport:
reportType = sentStats.v2MembershipReport
case header.IGMPLeaveGroup:
reportType = sentStats.leaveGroup
default:
panic(fmt.Sprintf("unrecognized igmp type = %d", igmpType))
}
return igmp.writePacketInner(
igmpView,
reportType,
header.IPv4OptionsSerializer{
&header.IPv4SerializableRouterAlertOption{},
},
destAddress,
)
}
// +checklocksread:igmp.ep.mu
func (igmp *igmpState) writePacketInner(buf *bufferv2.View, reportStat tcpip.MultiCounterStat, options header.IPv4OptionsSerializer, destAddress tcpip.Address) (bool, tcpip.Error) {
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(igmp.ep.MaxHeaderLength()),
Payload: bufferv2.MakeWithView(igmpView),
Payload: bufferv2.MakeWithView(buf),
})
defer pkt.DecRef()
@@ -335,9 +488,7 @@ func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip
Protocol: header.IGMPProtocolNumber,
TTL: header.IGMPTTL,
TOS: stack.DefaultTOS,
}, header.IPv4OptionsSerializer{
&header.IPv4SerializableRouterAlertOption{},
}); err != nil {
}, options); err != nil {
panic(fmt.Sprintf("failed to add IP header: %s", err))
}
@@ -346,16 +497,7 @@ func (igmp *igmpState) writePacket(destAddress tcpip.Address, groupAddress tcpip
sentStats.dropped.Increment()
return false, err
}
switch igmpType {
case header.IGMPv1MembershipReport:
sentStats.v1MembershipReport.Increment()
case header.IGMPv2MembershipReport:
sentStats.v2MembershipReport.Increment()
case header.IGMPLeaveGroup:
sentStats.leaveGroup.Increment()
default:
panic(fmt.Sprintf("unrecognized igmp type = %d", igmpType))
}
reportStat.Increment()
return true, nil
}
+149 -74
View File
@@ -25,6 +25,7 @@ import (
"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/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/testutil"
@@ -38,9 +39,10 @@ const (
)
var (
stackAddr = testutil.MustParse4("10.0.0.1")
remoteAddr = testutil.MustParse4("10.0.0.2")
multicastAddr = testutil.MustParse4("224.0.0.3")
stackAddr = testutil.MustParse4("10.0.0.1")
remoteAddr = testutil.MustParse4("10.0.0.2")
multicastAddr = testutil.MustParse4("224.0.0.3")
unusedMulticastAddr = testutil.MustParse4("224.0.0.4")
)
// validateIgmpPacket checks that a passed packet is an IPv4 IGMP packet sent
@@ -59,12 +61,35 @@ func validateIgmpPacket(t *testing.T, pkt stack.PacketBufferPtr, igmpType header
checker.IPv4RouterAlert(),
checker.IGMP(
checker.IGMPType(igmpType),
checker.IGMPMaxRespTime(header.DecisecondToDuration(maxRespTime)),
checker.IGMPMaxRespTime(header.DecisecondToDuration(uint16(maxRespTime))),
checker.IGMPGroupAddress(groupAddress),
),
)
}
func validateIgmpv3ReportPacket(t *testing.T, pkt stack.PacketBufferPtr, srcAddr, groupAddress tcpip.Address) {
t.Helper()
payload := stack.PayloadSince(pkt.NetworkHeader())
defer payload.Release()
checker.IPv4(t, payload,
checker.SrcAddr(srcAddr),
checker.DstAddr(header.IGMPv3RoutersAddress),
// TTL for an IGMP message must be 1 as per RFC 2236 section 2.
checker.TTL(1),
checker.IPv4RouterAlert(),
checker.IGMPv3Report(header.IGMPv3ReportSerializer{
Records: []header.IGMPv3ReportGroupAddressRecordSerializer{
{
RecordType: header.IGMPv3ReportRecordChangeToExcludeMode,
GroupAddress: groupAddress,
Sources: nil,
},
},
}),
)
}
type igmpTestContext struct {
s *stack.Stack
ep *channel.Endpoint
@@ -157,17 +182,17 @@ func TestIGMPV1Present(t *testing.T) {
t.Fatalf("JoinGroup(ipv4, nic, %s) = %s", multicastAddr, err)
}
// This NIC will send an IGMPv2 report immediately, before this test can get
// This NIC will send an IGMPv3 report immediately, before this test can get
// the IGMPv1 General Membership Query in.
{
p := e.Read()
if p.IsNil() {
t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
t.Fatal("unable to Read IGMP packet, expected V3MembershipReport")
}
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 1 {
t.Fatalf("got V2MembershipReport messages sent = %d, want = 1", got)
if got := s.Stats().IGMP.PacketsSent.V3MembershipReport.Value(); got != 1 {
t.Fatalf("got V3MembershipReport messages sent = %d, want = 1", got)
}
validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
validateIgmpv3ReportPacket(t, p, stackAddr, multicastAddr)
p.DecRef()
}
if t.Failed() {
@@ -176,9 +201,8 @@ func TestIGMPV1Present(t *testing.T) {
// Inject an IGMPv1 General Membership Query which is identical to a standard
// membership query except the Max Response Time is set to 0, which will tell
// the stack that this is a router using IGMPv1. Send it to the all systems
// group which is the only group this host belongs to.
createAndInjectIGMPPacket(e, header.IGMPMembershipQuery, 0, defaultTTL, remoteAddr, stackAddr, header.IPv4AllSystems, true /* hasRouterAlertOption */)
// the stack that this is a router using IGMPv1.
createAndInjectIGMPPacket(e, header.IGMPMembershipQuery, 0, defaultTTL, remoteAddr, stackAddr, multicastAddr, true /* hasRouterAlertOption */)
if got := s.Stats().IGMP.PacketsReceived.MembershipQuery.Value(); got != 1 {
t.Fatalf("got Membership Queries received = %d, want = 1", got)
}
@@ -219,78 +243,129 @@ func TestIGMPV1Present(t *testing.T) {
if p.IsNil() {
t.Fatal("unable to Read IGMP packet, expected V2MembershipReport")
}
if got := s.Stats().IGMP.PacketsSent.V2MembershipReport.Value(); got != 2 {
t.Fatalf("got V2MembershipReport messages sent = %d, want = 2", got)
if got := s.Stats().IGMP.PacketsSent.V3MembershipReport.Value(); got != 2 {
t.Fatalf("got V3MembershipReport messages sent = %d, want = 2", got)
}
validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
validateIgmpv3ReportPacket(t, p, stackAddr, multicastAddr)
p.DecRef()
}
}
func TestSendQueuedIGMPReports(t *testing.T) {
ctx := newIGMPTestContext(t, true /* igmpEnabled */)
defer ctx.cleanup()
s := ctx.s
e := ctx.ep
clock := ctx.clock
tests := []struct {
name string
v2Compatibility bool
validate func(t *testing.T, pkt stack.PacketBufferPtr, localAddress tcpip.Address, groupAddress tcpip.Address)
checkStats func(*testing.T, *stack.Stack, uint64, uint64, uint64)
}{
{
name: "V2 Compatibility",
v2Compatibility: true,
validate: func(t *testing.T, pkt stack.PacketBufferPtr, localAddress tcpip.Address, groupAddress tcpip.Address) {
t.Helper()
// Joining a group without an assigned address should queue IGMP packets; none
// should be sent without an assigned address.
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
t.Fatalf("JoinGroup(%d, %d, %s): %s", ipv4.ProtocolNumber, nicID, multicastAddr, err)
}
reportStat := s.Stats().IGMP.PacketsSent.V2MembershipReport
if got := reportStat.Value(); got != 0 {
t.Errorf("got reportStat.Value() = %d, want = 0", got)
}
clock.Advance(time.Hour)
if p := e.Read(); !p.IsNil() {
t.Fatalf("got unexpected packet = %#v", p)
}
// The initial set of IGMP reports that were queued should be sent once an
// address is assigned.
protocolAddr := tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: stackAddr,
PrefixLen: defaultPrefixLength,
validateIgmpPacket(t, pkt, header.IGMPv2MembershipReport, 0 /* maxRespTime */, localAddress, groupAddress, groupAddress)
},
checkStats: iptestutil.CheckIGMPv2Stats,
},
{
name: "V3",
v2Compatibility: false,
validate: validateIgmpv3ReportPacket,
checkStats: iptestutil.CheckIGMPv3Stats,
},
}
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
if got := reportStat.Value(); got != 1 {
t.Errorf("got reportStat.Value() = %d, want = 1", got)
}
if p := e.Read(); p.IsNil() {
t.Error("expected to send an IGMP membership report")
} else {
validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
p.DecRef()
}
if t.Failed() {
t.FailNow()
}
clock.Advance(ipv4.UnsolicitedReportIntervalMax)
if got := reportStat.Value(); got != 2 {
t.Errorf("got reportStat.Value() = %d, want = 2", got)
}
if p := e.Read(); p.IsNil() {
t.Error("expected to send an IGMP membership report")
} else {
validateIgmpPacket(t, p, header.IGMPv2MembershipReport, 0, stackAddr, multicastAddr, multicastAddr)
p.DecRef()
}
if t.Failed() {
t.FailNow()
}
// Should have no more packets to send after the initial set of unsolicited
// reports.
clock.Advance(time.Hour)
if p := e.Read(); !p.IsNil() {
t.Fatalf("got unexpected packet = %#v", p)
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := newIGMPTestContext(t, true /* igmpEnabled */)
defer ctx.cleanup()
s := ctx.s
e := ctx.ep
clock := ctx.clock
checkVersion := func() {
if test.v2Compatibility {
createAndInjectIGMPPacket(
e,
header.IGMPMembershipQuery,
1, /* maxRespTime */
header.IGMPTTL,
remoteAddr,
header.IPv4AllSystems,
unusedMulticastAddr,
true, /* hasRouterAlertOption */
)
}
}
protocolAddr := tcpip.ProtocolAddress{
Protocol: ipv4.ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: stackAddr,
PrefixLen: defaultPrefixLength,
},
}
// Multicast traffic is not accepted unless we have an address so add an
// address and check the version which receives a multicast packet.
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
checkVersion()
if err := s.RemoveAddress(nicID, protocolAddr.AddressWithPrefix.Address); err != nil {
t.Fatalf("RemoveAddress(%d, %s): %s", nicID, protocolAddr.AddressWithPrefix.Address, err)
}
var reportCounter uint64
var doneCounter uint64
var reportV2Counter uint64
test.checkStats(t, s, reportCounter, doneCounter, reportV2Counter)
// Joining a group without an assigned address should queue IGMP packets; none
// should be sent without an assigned address.
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, multicastAddr); err != nil {
t.Fatalf("JoinGroup(%d, %d, %s): %s", ipv4.ProtocolNumber, nicID, multicastAddr, err)
}
test.checkStats(t, s, reportCounter, doneCounter, reportV2Counter)
if p := e.Read(); !p.IsNil() {
t.Fatalf("got unexpected packet = %#v", p)
}
// The initial set of IGMP reports that were queued should be sent once an
// address is assigned.
if err := s.AddProtocolAddress(nicID, protocolAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protocolAddr, err)
}
reportCounter++
test.checkStats(t, s, reportCounter, doneCounter, reportV2Counter)
if p := e.Read(); p.IsNil() {
t.Error("expected to send an IGMP membership report")
} else {
test.validate(t, p, stackAddr, multicastAddr)
p.DecRef()
}
if t.Failed() {
t.FailNow()
}
clock.Advance(ipv4.UnsolicitedReportIntervalMax)
reportCounter++
test.checkStats(t, s, reportCounter, doneCounter, reportV2Counter)
if p := e.Read(); p.IsNil() {
t.Error("expected to send an IGMP membership report")
} else {
test.validate(t, p, stackAddr, multicastAddr)
p.DecRef()
}
if t.Failed() {
t.FailNow()
}
// Should have no more packets to send after the initial set of unsolicited
// reports.
clock.Advance(time.Hour)
if p := e.Read(); !p.IsNil() {
t.Fatalf("got unexpected packet = %#v", p)
}
})
}
}
+2
View File
@@ -131,6 +131,7 @@ type multiCounterIGMPPacketStats struct {
membershipQuery tcpip.MultiCounterStat
v1MembershipReport tcpip.MultiCounterStat
v2MembershipReport tcpip.MultiCounterStat
v3MembershipReport tcpip.MultiCounterStat
leaveGroup tcpip.MultiCounterStat
}
@@ -138,6 +139,7 @@ func (m *multiCounterIGMPPacketStats) init(a, b *tcpip.IGMPPacketStats) {
m.membershipQuery.Init(a.MembershipQuery, b.MembershipQuery)
m.v1MembershipReport.Init(a.V1MembershipReport, b.V1MembershipReport)
m.v2MembershipReport.Init(a.V2MembershipReport, b.V2MembershipReport)
m.v3MembershipReport.Init(a.V3MembershipReport, b.V3MembershipReport)
m.leaveGroup.Init(a.LeaveGroup, b.LeaveGroup)
}
+1
View File
@@ -75,6 +75,7 @@ go_test(
"//pkg/tcpip/faketime",
"//pkg/tcpip/header",
"//pkg/tcpip/link/channel",
"//pkg/tcpip/network/internal/testutil",
"//pkg/tcpip/stack",
"//pkg/tcpip/testutil",
],
+14 -4
View File
@@ -852,12 +852,18 @@ func (e *endpoint) handleICMP(pkt stack.PacketBufferPtr, hasFragmentHeader bool,
return
}
case header.ICMPv6MulticastListenerQuery, header.ICMPv6MulticastListenerReport, header.ICMPv6MulticastListenerDone:
case header.ICMPv6MulticastListenerQuery,
header.ICMPv6MulticastListenerReport,
header.ICMPv6MulticastListenerV2Report,
header.ICMPv6MulticastListenerDone:
icmpBody := h.MessageBody()
switch icmpType {
case header.ICMPv6MulticastListenerQuery:
received.multicastListenerQuery.Increment()
case header.ICMPv6MulticastListenerReport:
received.multicastListenerReport.Increment()
case header.ICMPv6MulticastListenerV2Report:
received.multicastListenerReportV2.Increment()
case header.ICMPv6MulticastListenerDone:
received.multicastListenerDone.Increment()
default:
@@ -872,13 +878,17 @@ func (e *endpoint) handleICMP(pkt stack.PacketBufferPtr, hasFragmentHeader bool,
switch icmpType {
case header.ICMPv6MulticastListenerQuery:
e.mu.Lock()
e.mu.mld.handleMulticastListenerQuery(header.MLD(h.MessageBody()))
if len(icmpBody) >= header.MLDv2QueryMinimumSize {
e.mu.mld.handleMulticastListenerQueryV2(header.MLDv2Query(icmpBody))
} else {
e.mu.mld.handleMulticastListenerQuery(header.MLD(icmpBody))
}
e.mu.Unlock()
case header.ICMPv6MulticastListenerReport:
e.mu.Lock()
e.mu.mld.handleMulticastListenerReport(header.MLD(h.MessageBody()))
e.mu.mld.handleMulticastListenerReport(header.MLD(icmpBody))
e.mu.Unlock()
case header.ICMPv6MulticastListenerDone:
case header.ICMPv6MulticastListenerDone, header.ICMPv6MulticastListenerV2Report:
default:
panic(fmt.Sprintf("unrecognized MLD message = %d", icmpType))
}
+6
View File
@@ -348,6 +348,12 @@ func TestICMPCounts(t *testing.T) {
includeRouterAlert: true,
size: header.MLDMinimumSize + header.ICMPv6HeaderSize,
},
{
typ: header.ICMPv6MulticastListenerV2Report,
hopLimit: header.MLDHopLimit,
includeRouterAlert: true,
size: header.MLDv2ReportMinimumSize + header.ICMPv6HeaderSize,
},
{
typ: header.ICMPv6MulticastListenerDone,
hopLimit: header.MLDHopLimit,

Some files were not shown because too many files have changed in this diff Show More