mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
5bb418ecdb
commit
4632d45dd8
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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",
|
||||
],
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user