mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Support parsing/serializing MLDv2/IGMPv3 messages
Updates #8346 PiperOrigin-RevId: 499920816
This commit is contained in:
committed by
gVisor bot
parent
0781095639
commit
f67a389f0a
@@ -13,12 +13,15 @@ go_library(
|
||||
"icmpv4.go",
|
||||
"icmpv6.go",
|
||||
"igmp.go",
|
||||
"igmpv3.go",
|
||||
"interfaces.go",
|
||||
"ipv4.go",
|
||||
"ipv6.go",
|
||||
"ipv6_extension_headers.go",
|
||||
"ipv6_fragment.go",
|
||||
"mld.go",
|
||||
"mldv2.go",
|
||||
"mldv2_igmpv3_common.go",
|
||||
"ndp_neighbor_advert.go",
|
||||
"ndp_neighbor_solicit.go",
|
||||
"ndp_options.go",
|
||||
|
||||
@@ -94,6 +94,8 @@ const (
|
||||
// IGMPLeaveGroup indicates that the message type is a Leave Group
|
||||
// notification message.
|
||||
IGMPLeaveGroup IGMPType = 0x17
|
||||
// IGMPv3MembershipReport indicates that the message type is a IGMPv3 report.
|
||||
IGMPv3MembershipReport IGMPType = 0x22
|
||||
)
|
||||
|
||||
// Type is the IGMP type field.
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
package header_test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/testutil"
|
||||
@@ -109,3 +112,284 @@ func TestDecisecondToDuration(t *testing.T) {
|
||||
t.Fatalf("got header.DecisecondToDuration(%d) = %s, want = %s", valueInDeciseconds, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIGMPv3Query(t *testing.T) {
|
||||
const (
|
||||
exponentialQueryIntervalStartCode = 128
|
||||
mantQQICBits = 4
|
||||
)
|
||||
|
||||
qrvs := []uint8{0, 1, 2, 3, 4, 5, 6, 7}
|
||||
|
||||
type qqicTest struct {
|
||||
val uint8
|
||||
expectedInterval time.Duration
|
||||
}
|
||||
|
||||
exponentialQQIC := func(mant, exp uint8) qqicTest {
|
||||
return qqicTest{
|
||||
val: exponentialQueryIntervalStartCode | mant | exp<<mantQQICBits,
|
||||
expectedInterval: ((time.Duration(mant) | 0x10) << (time.Duration(exp) + 3)) * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
queryIntervalCodes := []qqicTest{
|
||||
{
|
||||
val: 0,
|
||||
expectedInterval: 0,
|
||||
},
|
||||
{
|
||||
val: 1,
|
||||
expectedInterval: time.Second,
|
||||
},
|
||||
{
|
||||
val: exponentialQueryIntervalStartCode - 1,
|
||||
expectedInterval: (exponentialQueryIntervalStartCode - 1) * time.Second,
|
||||
},
|
||||
{
|
||||
val: exponentialQueryIntervalStartCode,
|
||||
expectedInterval: exponentialQueryIntervalStartCode * time.Second,
|
||||
},
|
||||
exponentialQQIC(0, 0),
|
||||
exponentialQQIC(1, 0),
|
||||
exponentialQQIC(0, 1),
|
||||
exponentialQQIC(1, 1),
|
||||
}
|
||||
|
||||
sourceAddrs := []tcpip.Address{
|
||||
testutil.MustParse4("1.0.0.1"),
|
||||
testutil.MustParse4("2.0.0.2"),
|
||||
testutil.MustParse4("3.0.0.3"),
|
||||
}
|
||||
|
||||
sources := []struct {
|
||||
count uint16
|
||||
expectedOK bool
|
||||
}{
|
||||
{
|
||||
count: 0,
|
||||
expectedOK: true,
|
||||
},
|
||||
{
|
||||
count: 0,
|
||||
expectedOK: true,
|
||||
},
|
||||
{
|
||||
count: 1,
|
||||
expectedOK: true,
|
||||
},
|
||||
{
|
||||
count: uint16(len(sourceAddrs)),
|
||||
expectedOK: true,
|
||||
},
|
||||
{
|
||||
count: uint16(len(sourceAddrs) + 1),
|
||||
expectedOK: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, respCode := range []uint8{0x01, 0x10} {
|
||||
for _, qrv := range qrvs {
|
||||
for _, qqic := range queryIntervalCodes {
|
||||
for _, source := range sources {
|
||||
t.Run(fmt.Sprintf("MaxRespCode=%d QRV=%d QQIC=%d Sources=%d", respCode, qrv, qqic.val, source.count), func(t *testing.T) {
|
||||
b := []byte{
|
||||
// Type,
|
||||
0x11,
|
||||
|
||||
// Maximum Response Code
|
||||
0,
|
||||
|
||||
// Checksum
|
||||
0, 0,
|
||||
|
||||
// GroupAddress
|
||||
1, 2, 3, 4,
|
||||
|
||||
// Resv, S, QRV
|
||||
qrv,
|
||||
|
||||
// QQIC
|
||||
qqic.val,
|
||||
|
||||
// Number of Sources
|
||||
0, 0,
|
||||
|
||||
// Sources
|
||||
1, 0, 0, 1,
|
||||
2, 0, 0, 2,
|
||||
3, 0, 0, 3,
|
||||
}
|
||||
|
||||
b[1] = respCode
|
||||
binary.BigEndian.PutUint16(b[10:], source.count)
|
||||
|
||||
query := header.IGMPv3Query(b)
|
||||
if got := query.MaximumResponseCode(); got != respCode {
|
||||
t.Errorf("got query.MaximumResponseCode() = %d, want = %d", got, respCode)
|
||||
}
|
||||
if got := query.QuerierRobustnessVariable(); got != qrv {
|
||||
t.Errorf("got query.QuerierRobustnessVariable() = %d, want = %d", got, qrv)
|
||||
}
|
||||
if got := query.QuerierQueryInterval(); got != qqic.expectedInterval {
|
||||
t.Errorf("got query.QuerierQueryInterval() = %s, want = %s", got, qqic.expectedInterval)
|
||||
}
|
||||
if got, want := query.GroupAddress(), tcpip.Address([]byte{1, 2, 3, 4}); got != want {
|
||||
t.Errorf("got query.GroupAddress() = %s, want = %s", got, want)
|
||||
}
|
||||
|
||||
iterator, ok := query.Sources()
|
||||
if ok != source.expectedOK {
|
||||
t.Errorf("got query.Sources() = (_, %t), want = (_, %t)", ok, source.expectedOK)
|
||||
}
|
||||
if !source.expectedOK {
|
||||
return
|
||||
}
|
||||
|
||||
sourceAddrs := sourceAddrs[:source.count]
|
||||
for i := uint16(0); ; i++ {
|
||||
if len(sourceAddrs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
source, ok := iterator.Next()
|
||||
if !ok {
|
||||
t.Fatalf("expected %d-th source", i)
|
||||
}
|
||||
if source != sourceAddrs[0] {
|
||||
t.Errorf("got %d-th source = %s, want = %s", i, source, sourceAddrs[0])
|
||||
}
|
||||
|
||||
sourceAddrs = sourceAddrs[1:]
|
||||
}
|
||||
if len(sourceAddrs) != 0 {
|
||||
t.Errorf("missing sources = %#v", sourceAddrs)
|
||||
}
|
||||
if source, ok := iterator.Next(); ok {
|
||||
t.Errorf("unexpected source = %s", source)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIGMPv3Report(t *testing.T) {
|
||||
var (
|
||||
mcastAddr1 = testutil.MustParse4("224.1.0.1")
|
||||
mcastAddr2 = testutil.MustParse4("224.2.0.2")
|
||||
mcastAddr3 = testutil.MustParse4("224.3.0.3")
|
||||
|
||||
srcAddr1 = testutil.MustParse4("1.0.0.1")
|
||||
srcAddr2 = testutil.MustParse4("2.0.0.2")
|
||||
srcAddr3 = testutil.MustParse4("3.0.0.3")
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
serializer header.IGMPv3ReportSerializer
|
||||
}{
|
||||
{
|
||||
name: "zero reports",
|
||||
serializer: header.IGMPv3ReportSerializer{},
|
||||
},
|
||||
{
|
||||
name: "one record with one source",
|
||||
serializer: header.IGMPv3ReportSerializer{
|
||||
Records: []header.IGMPv3ReportGroupAddressRecordSerializer{
|
||||
{
|
||||
RecordType: header.IGMPv3ReportRecordModeIsInclude,
|
||||
GroupAddress: mcastAddr1,
|
||||
Sources: []tcpip.Address{srcAddr1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple records with multiple sources",
|
||||
serializer: header.IGMPv3ReportSerializer{
|
||||
Records: []header.IGMPv3ReportGroupAddressRecordSerializer{
|
||||
{
|
||||
RecordType: header.IGMPv3ReportRecordModeIsInclude,
|
||||
GroupAddress: mcastAddr1,
|
||||
Sources: nil,
|
||||
},
|
||||
{
|
||||
RecordType: header.IGMPv3ReportRecordModeIsExclude,
|
||||
GroupAddress: mcastAddr2,
|
||||
Sources: []tcpip.Address{srcAddr1, srcAddr2, srcAddr3},
|
||||
},
|
||||
{
|
||||
RecordType: header.IGMPv3ReportRecordChangeToIncludeMode,
|
||||
GroupAddress: mcastAddr3,
|
||||
Sources: []tcpip.Address{srcAddr1, srcAddr2},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
b := make([]byte, test.serializer.Length())
|
||||
test.serializer.SerializeInto(b)
|
||||
|
||||
report := header.IGMPv3Report(b)
|
||||
expectedRecords := test.serializer.Records
|
||||
|
||||
records := report.GroupAddressRecords()
|
||||
for {
|
||||
if len(expectedRecords) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
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 {
|
||||
if len(expectedSources) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,445 @@
|
||||
// Copyright 2022 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 header
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
const (
|
||||
// IGMPv3QueryMinimumSize is the mimum size of a valid IGMPv3 query,
|
||||
// as per RFC 3376 section 4.1.
|
||||
IGMPv3QueryMinimumSize = 12
|
||||
|
||||
igmpv3QueryMaxRespCodeOffset = 1
|
||||
igmpv3QueryGroupAddressOffset = 4
|
||||
igmpv3QueryResvSQRVOffset = 8
|
||||
igmpv3QueryQRVMask = 0b111
|
||||
igmpv3QueryQQICOffset = 9
|
||||
igmpv3QueryNumberOfSourcesOffset = 10
|
||||
igmpv3QuerySourcesOffset = 12
|
||||
)
|
||||
|
||||
// IGMPv3Query is an IGMPv3 query message.
|
||||
//
|
||||
// As per RFC 3376 section 4.1,
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type = 0x11 | Max Resp Code | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Group Address |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Resv |S| QRV | QQIC | Number of Sources (N) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Source Address [1] |
|
||||
// +- -+
|
||||
// | Source Address [2] |
|
||||
// +- . -+
|
||||
// . . .
|
||||
// . . .
|
||||
// +- -+
|
||||
// | Source Address [N] |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type IGMPv3Query IGMP
|
||||
|
||||
// MaximumResponseCode returns the Maximum Response Code.
|
||||
func (i IGMPv3Query) MaximumResponseCode() uint8 {
|
||||
return i[igmpv3QueryMaxRespCodeOffset]
|
||||
}
|
||||
|
||||
// GroupAddress returns the group address.
|
||||
func (i IGMPv3Query) GroupAddress() tcpip.Address {
|
||||
return tcpip.Address(i[igmpv3QueryGroupAddressOffset:][:IPv4AddressSize])
|
||||
}
|
||||
|
||||
// QuerierRobustnessVariable returns the querier's robustness variable.
|
||||
func (i IGMPv3Query) QuerierRobustnessVariable() uint8 {
|
||||
return i[igmpv3QueryResvSQRVOffset] & igmpv3QueryQRVMask
|
||||
}
|
||||
|
||||
// QuerierQueryInterval returns the querier's query interval.
|
||||
func (i IGMPv3Query) QuerierQueryInterval() time.Duration {
|
||||
return mldv2AndIGMPv3QuerierQueryCodeToInterval(i[igmpv3QueryQQICOffset])
|
||||
}
|
||||
|
||||
// Sources returns an iterator over source addresses in the query.
|
||||
//
|
||||
// Returns false if the message cannot hold the expected number of sources.
|
||||
func (i IGMPv3Query) Sources() (AddressIterator, bool) {
|
||||
return makeAddressIterator(
|
||||
i[igmpv3QuerySourcesOffset:],
|
||||
binary.BigEndian.Uint16(i[igmpv3QueryNumberOfSourcesOffset:]),
|
||||
IPv4AddressSize,
|
||||
)
|
||||
}
|
||||
|
||||
// IGMPv3ReportRecordType is the type of an IGMPv3 multicast address record
|
||||
// found in an IGMPv3 report, as per RFC 3810 section 5.2.12.
|
||||
type IGMPv3ReportRecordType int
|
||||
|
||||
// IGMPv3 multicast address record types, as per RFC 3810 section 5.2.12.
|
||||
const (
|
||||
IGMPv3ReportRecordModeIsInclude IGMPv3ReportRecordType = 1
|
||||
IGMPv3ReportRecordModeIsExclude IGMPv3ReportRecordType = 2
|
||||
IGMPv3ReportRecordChangeToIncludeMode IGMPv3ReportRecordType = 3
|
||||
IGMPv3ReportRecordChangeToExcludeMode IGMPv3ReportRecordType = 4
|
||||
IGMPv3ReportRecordAllowNewSources IGMPv3ReportRecordType = 5
|
||||
IGMPv3ReportRecordBlockOldSources IGMPv3ReportRecordType = 6
|
||||
)
|
||||
|
||||
const (
|
||||
igmpv3ReportGroupAddressRecordMinimumSize = 8
|
||||
igmpv3ReportGroupAddressRecordTypeOffset = 0
|
||||
igmpv3ReportGroupAddressRecordAuxDataLenOffset = 1
|
||||
igmpv3ReportGroupAddressRecordAuxDataLenUnits = 4
|
||||
igmpv3ReportGroupAddressRecordNumberOfSourcesOffset = 2
|
||||
igmpv3ReportGroupAddressRecordGroupAddressOffset = 4
|
||||
igmpv3ReportGroupAddressRecordSourcesOffset = 8
|
||||
)
|
||||
|
||||
// IGMPv3ReportGroupAddressRecordSerializer is an IGMPv3 Multicast Address
|
||||
// Record serializer.
|
||||
//
|
||||
// As per RFC 3810 section 5.2, a Multicast Address Record has the following
|
||||
// internal format:
|
||||
//
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Record Type | Aux Data Len | Number of Sources (N) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Multicast Address *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [1] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [2] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// . . .
|
||||
// . . .
|
||||
// . . .
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [N] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Auxiliary Data .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type IGMPv3ReportGroupAddressRecordSerializer struct {
|
||||
RecordType IGMPv3ReportRecordType
|
||||
GroupAddress tcpip.Address
|
||||
Sources []tcpip.Address
|
||||
}
|
||||
|
||||
// Length returns the number of bytes this serializer would occupy.
|
||||
func (s *IGMPv3ReportGroupAddressRecordSerializer) Length() int {
|
||||
return igmpv3ReportGroupAddressRecordSourcesOffset + len(s.Sources)*IPv4AddressSize
|
||||
}
|
||||
|
||||
func copyIPv4Address(dst []byte, src tcpip.Address) {
|
||||
if n := copy(dst, src); n != IPv4AddressSize {
|
||||
panic(fmt.Sprintf("got copy(...) = %d, want = %d", n, IPv4AddressSize))
|
||||
}
|
||||
}
|
||||
|
||||
// SerializeInto serializes the record into the buffer.
|
||||
//
|
||||
// Panics if the buffer does not have enough space to fit the record.
|
||||
func (s *IGMPv3ReportGroupAddressRecordSerializer) SerializeInto(b []byte) {
|
||||
b[igmpv3ReportGroupAddressRecordTypeOffset] = byte(s.RecordType)
|
||||
b[igmpv3ReportGroupAddressRecordAuxDataLenOffset] = 0
|
||||
binary.BigEndian.PutUint16(b[igmpv3ReportGroupAddressRecordNumberOfSourcesOffset:], uint16(len(s.Sources)))
|
||||
copyIPv4Address(b[igmpv3ReportGroupAddressRecordGroupAddressOffset:], s.GroupAddress)
|
||||
b = b[igmpv3ReportGroupAddressRecordSourcesOffset:]
|
||||
for _, source := range s.Sources {
|
||||
copyIPv4Address(b, source)
|
||||
b = b[IPv4AddressSize:]
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
igmpv3ReportTypeOffset = 0
|
||||
igmpv3ReportReserved1Offset = 1
|
||||
igmpv3ReportReserved2Offset = 4
|
||||
igmpv3ReportNumberOfGroupAddressRecordsOffset = 6
|
||||
igmpv3ReportGroupAddressRecordsOffset = 8
|
||||
)
|
||||
|
||||
// IGMPv3ReportSerializer is an MLD Version 2 Report serializer.
|
||||
//
|
||||
// As per RFC 3810 section 5.2,
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type = 143 | Reserved | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Reserved |Nr of Mcast Address Records (M)|
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [1] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [2] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | . |
|
||||
// . . .
|
||||
// | . |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [M] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type IGMPv3ReportSerializer struct {
|
||||
Records []IGMPv3ReportGroupAddressRecordSerializer
|
||||
}
|
||||
|
||||
// Length returns the number of bytes this serializer would occupy.
|
||||
func (s *IGMPv3ReportSerializer) Length() int {
|
||||
ret := igmpv3ReportGroupAddressRecordsOffset
|
||||
for _, record := range s.Records {
|
||||
ret += record.Length()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// SerializeInto serializes the report into the buffer.
|
||||
//
|
||||
// Panics if the buffer does not have enough space to fit the report.
|
||||
func (s *IGMPv3ReportSerializer) SerializeInto(b []byte) {
|
||||
b[igmpv3ReportTypeOffset] = byte(IGMPv3MembershipReport)
|
||||
b[igmpv3ReportReserved1Offset] = 0
|
||||
binary.BigEndian.PutUint16(b[igmpv3ReportReserved2Offset:], 0)
|
||||
binary.BigEndian.PutUint16(b[igmpv3ReportNumberOfGroupAddressRecordsOffset:], uint16(len(s.Records)))
|
||||
b = b[igmpv3ReportGroupAddressRecordsOffset:]
|
||||
for _, record := range s.Records {
|
||||
len := record.Length()
|
||||
record.SerializeInto(b[:len])
|
||||
b = b[len:]
|
||||
}
|
||||
}
|
||||
|
||||
// IGMPv3ReportGroupAddressRecord is an IGMPv3 record.
|
||||
//
|
||||
// As per RFC 3810 section 5.2, a Multicast Address Record has the following
|
||||
// internal format:
|
||||
//
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Record Type | Aux Data Len | Number of Sources (N) |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Multicast Address *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [1] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [2] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +- -+
|
||||
// . . .
|
||||
// . . .
|
||||
// . . .
|
||||
// +- -+
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// * Source Address [N] *
|
||||
// | |
|
||||
// * *
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Auxiliary Data .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type IGMPv3ReportGroupAddressRecord []byte
|
||||
|
||||
// RecordType returns the type of this record.
|
||||
func (r IGMPv3ReportGroupAddressRecord) RecordType() IGMPv3ReportRecordType {
|
||||
return IGMPv3ReportRecordType(r[igmpv3ReportGroupAddressRecordTypeOffset])
|
||||
}
|
||||
|
||||
// AuxDataLen returns the length of the auxillary data in this record.
|
||||
func (r IGMPv3ReportGroupAddressRecord) AuxDataLen() int {
|
||||
return int(r[igmpv3ReportGroupAddressRecordAuxDataLenOffset]) * igmpv3ReportGroupAddressRecordAuxDataLenUnits
|
||||
}
|
||||
|
||||
// numberOfSources returns the number of sources in this record.
|
||||
func (r IGMPv3ReportGroupAddressRecord) numberOfSources() uint16 {
|
||||
return binary.BigEndian.Uint16(r[igmpv3ReportGroupAddressRecordNumberOfSourcesOffset:])
|
||||
}
|
||||
|
||||
// GroupAddress returns the multicast address this record targets.
|
||||
func (r IGMPv3ReportGroupAddressRecord) GroupAddress() tcpip.Address {
|
||||
return tcpip.Address(r[igmpv3ReportGroupAddressRecordGroupAddressOffset:][:IPv4AddressSize])
|
||||
}
|
||||
|
||||
// Sources returns an iterator over source addresses in the query.
|
||||
//
|
||||
// Returns false if the message cannot hold the expected number of sources.
|
||||
func (r IGMPv3ReportGroupAddressRecord) Sources() (AddressIterator, bool) {
|
||||
expectedLen := int(r.numberOfSources()) * IPv4AddressSize
|
||||
b := r[igmpv3ReportGroupAddressRecordSourcesOffset:]
|
||||
if len(b) < expectedLen {
|
||||
return AddressIterator{}, false
|
||||
}
|
||||
return AddressIterator{addressSize: IPv4AddressSize, buf: bytes.NewBuffer(b[:expectedLen])}, true
|
||||
}
|
||||
|
||||
// IGMPv3Report is an IGMPv3 Report.
|
||||
//
|
||||
// As per RFC 3810 section 5.2,
|
||||
//
|
||||
// 0 1 2 3
|
||||
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Type = 143 | Reserved | Checksum |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | Reserved |Nr of Mcast Address Records (M)|
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [1] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [2] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | . |
|
||||
// . . .
|
||||
// | . |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
// | |
|
||||
// . .
|
||||
// . Multicast Address Record [M] .
|
||||
// . .
|
||||
// | |
|
||||
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
type IGMPv3Report []byte
|
||||
|
||||
// IGMPv3ReportGroupAddressRecordIterator is an iterator over IGMPv3 Multicast
|
||||
// Address Records.
|
||||
type IGMPv3ReportGroupAddressRecordIterator struct {
|
||||
recordsLeft uint16
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
// IGMPv3ReportGroupAddressRecordIteratorNextDisposition is the possible
|
||||
// return values from IGMPv3ReportGroupAddressRecordIterator.Next.
|
||||
type IGMPv3ReportGroupAddressRecordIteratorNextDisposition int
|
||||
|
||||
const (
|
||||
// IGMPv3ReportGroupAddressRecordIteratorNextOk indicates that a multicast
|
||||
// address record was yielded.
|
||||
IGMPv3ReportGroupAddressRecordIteratorNextOk IGMPv3ReportGroupAddressRecordIteratorNextDisposition = iota
|
||||
|
||||
// IGMPv3ReportGroupAddressRecordIteratorNextDone indicates that the iterator
|
||||
// has been exhausted.
|
||||
IGMPv3ReportGroupAddressRecordIteratorNextDone
|
||||
|
||||
// IGMPv3ReportGroupAddressRecordIteratorNextErrBufferTooShort indicates
|
||||
// that the iterator expected another record, but the buffer ended
|
||||
// prematurely.
|
||||
IGMPv3ReportGroupAddressRecordIteratorNextErrBufferTooShort
|
||||
)
|
||||
|
||||
// Next returns the next IGMPv3 Multicast Address Record.
|
||||
func (it *IGMPv3ReportGroupAddressRecordIterator) Next() (IGMPv3ReportGroupAddressRecord, IGMPv3ReportGroupAddressRecordIteratorNextDisposition) {
|
||||
if it.recordsLeft == 0 {
|
||||
return IGMPv3ReportGroupAddressRecord{}, IGMPv3ReportGroupAddressRecordIteratorNextDone
|
||||
}
|
||||
if it.buf.Len() < igmpv3ReportGroupAddressRecordMinimumSize {
|
||||
return IGMPv3ReportGroupAddressRecord{}, IGMPv3ReportGroupAddressRecordIteratorNextErrBufferTooShort
|
||||
}
|
||||
|
||||
hdr := IGMPv3ReportGroupAddressRecord(it.buf.Bytes())
|
||||
expectedLen := igmpv3ReportGroupAddressRecordMinimumSize +
|
||||
int(hdr.AuxDataLen()) + int(hdr.numberOfSources())*IPv4AddressSize
|
||||
|
||||
bytes := it.buf.Next(expectedLen)
|
||||
if len(bytes) < expectedLen {
|
||||
return IGMPv3ReportGroupAddressRecord{}, IGMPv3ReportGroupAddressRecordIteratorNextErrBufferTooShort
|
||||
}
|
||||
it.recordsLeft--
|
||||
return IGMPv3ReportGroupAddressRecord(bytes), IGMPv3ReportGroupAddressRecordIteratorNextOk
|
||||
}
|
||||
|
||||
// GroupAddressRecords returns an iterator of IGMPv3 Multicast Address
|
||||
// Records.
|
||||
func (i IGMPv3Report) GroupAddressRecords() IGMPv3ReportGroupAddressRecordIterator {
|
||||
return IGMPv3ReportGroupAddressRecordIterator{
|
||||
recordsLeft: binary.BigEndian.Uint16(i[igmpv3ReportNumberOfGroupAddressRecordsOffset:]),
|
||||
buf: bytes.NewBuffer(i[igmpv3ReportGroupAddressRecordsOffset:]),
|
||||
}
|
||||
}
|
||||
@@ -16,10 +16,12 @@ package header
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/testutil"
|
||||
)
|
||||
|
||||
func TestMLD(t *testing.T) {
|
||||
@@ -59,3 +61,327 @@ func TestMLD(t *testing.T) {
|
||||
t.Errorf("got mld.MulticastAddress() = %s, want = %s", got, multicastAddress)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMLDv2MaximumResponseDelay(t *testing.T) {
|
||||
const (
|
||||
exponentialResponseDelayStartCode = 32768
|
||||
mantMaxRespBits = 12
|
||||
)
|
||||
|
||||
type respCodeTest struct {
|
||||
maxResponseCode uint16
|
||||
expectedMaxResponseDelay time.Duration
|
||||
}
|
||||
|
||||
exponentialRespDelay := func(mant, exp uint16) respCodeTest {
|
||||
return respCodeTest{
|
||||
maxResponseCode: exponentialResponseDelayStartCode | mant | exp<<mantMaxRespBits,
|
||||
expectedMaxResponseDelay: ((time.Duration(mant) | 0x1000) << (time.Duration(exp) + 3)) * time.Millisecond,
|
||||
}
|
||||
}
|
||||
|
||||
tests := []respCodeTest{
|
||||
{
|
||||
maxResponseCode: 0,
|
||||
expectedMaxResponseDelay: 0,
|
||||
},
|
||||
{
|
||||
maxResponseCode: 1,
|
||||
expectedMaxResponseDelay: time.Millisecond,
|
||||
},
|
||||
{
|
||||
maxResponseCode: exponentialResponseDelayStartCode - 1,
|
||||
expectedMaxResponseDelay: (exponentialResponseDelayStartCode - 1) * time.Millisecond,
|
||||
},
|
||||
exponentialRespDelay(0, 0),
|
||||
exponentialRespDelay(1, 0),
|
||||
exponentialRespDelay(0, 1),
|
||||
exponentialRespDelay(1, 1),
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(fmt.Sprintf("Code=%d", test.maxResponseCode), func(t *testing.T) {
|
||||
if got := MLDv2MaximumResponseDelay(test.maxResponseCode); got != test.expectedMaxResponseDelay {
|
||||
t.Errorf("got MLDv2MaximumResponseDelay(%d) = %s, want = %s", test.maxResponseCode, got, test.expectedMaxResponseDelay)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMLDv2Query(t *testing.T) {
|
||||
const (
|
||||
exponentialQueryIntervalStartCode = 128
|
||||
mantQQICBits = 4
|
||||
)
|
||||
|
||||
qrvs := []uint8{0, 1, 2, 3, 4, 5, 6, 7}
|
||||
|
||||
type qqicTest struct {
|
||||
val uint8
|
||||
expectedInterval time.Duration
|
||||
}
|
||||
|
||||
exponentialQQIC := func(mant, exp uint8) qqicTest {
|
||||
return qqicTest{
|
||||
val: exponentialQueryIntervalStartCode | mant | exp<<mantQQICBits,
|
||||
expectedInterval: ((time.Duration(mant) | 0x10) << (time.Duration(exp) + 3)) * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
queryIntervalCodes := []qqicTest{
|
||||
{
|
||||
val: 0,
|
||||
expectedInterval: 0,
|
||||
},
|
||||
{
|
||||
val: 1,
|
||||
expectedInterval: time.Second,
|
||||
},
|
||||
{
|
||||
val: exponentialQueryIntervalStartCode - 1,
|
||||
expectedInterval: (exponentialQueryIntervalStartCode - 1) * time.Second,
|
||||
},
|
||||
{
|
||||
val: exponentialQueryIntervalStartCode,
|
||||
expectedInterval: exponentialQueryIntervalStartCode * time.Second,
|
||||
},
|
||||
exponentialQQIC(0, 0),
|
||||
exponentialQQIC(1, 0),
|
||||
exponentialQQIC(0, 1),
|
||||
exponentialQQIC(1, 1),
|
||||
}
|
||||
|
||||
sourceAddrs := []tcpip.Address{
|
||||
testutil.MustParse6("a00::a"),
|
||||
testutil.MustParse6("b00::b"),
|
||||
testutil.MustParse6("c00::c"),
|
||||
}
|
||||
|
||||
sources := []struct {
|
||||
count uint16
|
||||
expectedOK bool
|
||||
}{
|
||||
{
|
||||
count: 0,
|
||||
expectedOK: true,
|
||||
},
|
||||
{
|
||||
count: 0,
|
||||
expectedOK: true,
|
||||
},
|
||||
{
|
||||
count: 1,
|
||||
expectedOK: true,
|
||||
},
|
||||
{
|
||||
count: uint16(len(sourceAddrs)),
|
||||
expectedOK: true,
|
||||
},
|
||||
{
|
||||
count: uint16(len(sourceAddrs) + 1),
|
||||
expectedOK: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, respCode := range []uint16{0x0001, 0x0100} {
|
||||
for _, qrv := range qrvs {
|
||||
for _, qqic := range queryIntervalCodes {
|
||||
for _, source := range sources {
|
||||
t.Run(fmt.Sprintf("MaxRespCode=%d QRV=%d QQIC=%d Sources=%d", respCode, qrv, qqic.val, source.count), func(t *testing.T) {
|
||||
b := []byte{
|
||||
// Maximum Response Code
|
||||
0, 0,
|
||||
|
||||
// Reserved
|
||||
0, 0,
|
||||
|
||||
// MulticastAddress
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6,
|
||||
|
||||
// Resv, S, QRV
|
||||
qrv,
|
||||
|
||||
// QQIC
|
||||
qqic.val,
|
||||
|
||||
// Number of Sources
|
||||
0, 0,
|
||||
|
||||
// Sources
|
||||
0xA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xA,
|
||||
0xB, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xB,
|
||||
0xC, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xC,
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint16(b[mldMaximumResponseDelayOffset:], respCode)
|
||||
binary.BigEndian.PutUint16(b[mldv2QueryNumberOfSourcesOffset:], source.count)
|
||||
|
||||
query := MLDv2Query(b)
|
||||
if got := query.MaximumResponseCode(); got != respCode {
|
||||
t.Errorf("got query.MaximumResponseCode() = %d, want = %d", got, respCode)
|
||||
}
|
||||
if got := query.QuerierRobustnessVariable(); got != qrv {
|
||||
t.Errorf("got query.QuerierRobustnessVariable() = %d, want = %d", got, qrv)
|
||||
}
|
||||
if got := query.QuerierQueryInterval(); got != qqic.expectedInterval {
|
||||
t.Errorf("got query.QuerierQueryInterval() = %s, want = %s", got, qqic.expectedInterval)
|
||||
}
|
||||
if got, want := query.MulticastAddress(), tcpip.Address([]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}); got != want {
|
||||
t.Errorf("got query.MulticastAddress() = %s, want = %s", got, want)
|
||||
}
|
||||
|
||||
iterator, ok := query.Sources()
|
||||
if ok != source.expectedOK {
|
||||
t.Errorf("got query.Sources() = (_, %t), want = (_, %t)", ok, source.expectedOK)
|
||||
}
|
||||
if !source.expectedOK {
|
||||
return
|
||||
}
|
||||
|
||||
sourceAddrs := sourceAddrs[:source.count]
|
||||
for i := uint16(0); ; i++ {
|
||||
if len(sourceAddrs) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
source, ok := iterator.Next()
|
||||
if !ok {
|
||||
t.Fatalf("expected %d-th source", i)
|
||||
}
|
||||
if source != sourceAddrs[0] {
|
||||
t.Errorf("got %d-th source = %s, want = %s", i, source, sourceAddrs[0])
|
||||
}
|
||||
|
||||
sourceAddrs = sourceAddrs[1:]
|
||||
}
|
||||
if len(sourceAddrs) != 0 {
|
||||
t.Errorf("missing sources = %#v", sourceAddrs)
|
||||
}
|
||||
if source, ok := iterator.Next(); ok {
|
||||
t.Errorf("unexpected source = %s", source)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMLDv2Report(t *testing.T) {
|
||||
var (
|
||||
mcastAddr1 = testutil.MustParse6("ff02::a")
|
||||
mcastAddr2 = testutil.MustParse6("ff02::b")
|
||||
mcastAddr3 = testutil.MustParse6("ff02::c")
|
||||
|
||||
srcAddr1 = testutil.MustParse6("a::a")
|
||||
srcAddr2 = testutil.MustParse6("b::b")
|
||||
srcAddr3 = testutil.MustParse6("c::c")
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
serializer MLDv2ReportSerializer
|
||||
}{
|
||||
{
|
||||
name: "zero reports",
|
||||
serializer: MLDv2ReportSerializer{},
|
||||
},
|
||||
{
|
||||
name: "one record with one source",
|
||||
serializer: MLDv2ReportSerializer{
|
||||
Records: []MLDv2ReportMulticastAddressRecordSerializer{
|
||||
{
|
||||
RecordType: MLDv2ReportRecordModeIsInclude,
|
||||
MulticastAddress: mcastAddr1,
|
||||
Sources: []tcpip.Address{srcAddr1},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple records with multiple sources",
|
||||
serializer: MLDv2ReportSerializer{
|
||||
Records: []MLDv2ReportMulticastAddressRecordSerializer{
|
||||
{
|
||||
RecordType: MLDv2ReportRecordModeIsInclude,
|
||||
MulticastAddress: mcastAddr1,
|
||||
Sources: nil,
|
||||
},
|
||||
{
|
||||
RecordType: MLDv2ReportRecordModeIsExclude,
|
||||
MulticastAddress: mcastAddr2,
|
||||
Sources: []tcpip.Address{srcAddr1, srcAddr2, srcAddr3},
|
||||
},
|
||||
{
|
||||
RecordType: MLDv2ReportRecordChangeToIncludeMode,
|
||||
MulticastAddress: mcastAddr3,
|
||||
Sources: []tcpip.Address{srcAddr1, srcAddr2},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
b := make([]byte, test.serializer.Length())
|
||||
test.serializer.SerializeInto(b)
|
||||
|
||||
report := MLDv2Report(b)
|
||||
expectedRecords := test.serializer.Records
|
||||
|
||||
records := report.MulticastAddressRecords()
|
||||
for {
|
||||
if len(expectedRecords) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
record, res := records.Next()
|
||||
if res != MLDv2ReportMulticastAddressRecordIteratorNextOk {
|
||||
t.Fatalf("got records.Next() = (%#v, %d), want = (_, %d)", record, res, 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 {
|
||||
if len(expectedSources) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
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 != MLDv2ReportMulticastAddressRecordIteratorNextDone {
|
||||
t.Fatalf("got records.Next() = (%#v, %d), want = (_, %d)", record, res, MLDv2ReportMulticastAddressRecordIteratorNextDone)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,119 @@
|
||||
// Copyright 2022 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 header
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
)
|
||||
|
||||
func mldv2AndIGMPv3QuerierQueryCodeToInterval(code uint8) time.Duration {
|
||||
// MLDv2: As per RFC 3810 section 5.1.19,
|
||||
//
|
||||
// The Querier's Query Interval Code field specifies the [Query
|
||||
// Interval] used by the Querier. The actual interval, called the
|
||||
// Querier's Query Interval (QQI), is represented in units of seconds,
|
||||
// and is derived from the Querier's Query Interval Code as follows:
|
||||
//
|
||||
// If QQIC < 128, QQI = QQIC
|
||||
//
|
||||
// If QQIC >= 128, QQIC represents a floating-point value as follows:
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
// |1| exp | mant |
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// QQI = (mant | 0x10) << (exp + 3)
|
||||
//
|
||||
// Multicast routers that are not the current Querier adopt the QQI
|
||||
// value from the most recently received Query as their own [Query
|
||||
// Interval] value, unless that most recently received QQI was zero, in
|
||||
// which case the receiving routers use the default [Query Interval]
|
||||
// value specified in section 9.2.
|
||||
//
|
||||
// IGMPv3: As per RFC 3376 section 4.1.7,
|
||||
//
|
||||
// The Querier's Query Interval Code field specifies the [Query
|
||||
// Interval] used by the querier. The actual interval, called the
|
||||
// Querier's Query Interval (QQI), is represented in units of seconds
|
||||
// and is derived from the Querier's Query Interval Code as follows:
|
||||
//
|
||||
// If QQIC < 128, QQI = QQIC
|
||||
//
|
||||
// If QQIC >= 128, QQIC represents a floating-point value as follows:
|
||||
//
|
||||
// 0 1 2 3 4 5 6 7
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
// |1| exp | mant |
|
||||
// +-+-+-+-+-+-+-+-+
|
||||
//
|
||||
// QQI = (mant | 0x10) << (exp + 3)
|
||||
//
|
||||
// Multicast routers that are not the current querier adopt the QQI
|
||||
// value from the most recently received Query as their own [Query
|
||||
// Interval] value, unless that most recently received QQI was zero, in
|
||||
// which case the receiving routers use the default [Query Interval]
|
||||
// value specified in section 8.2.
|
||||
interval := time.Duration(code)
|
||||
if interval < 128 {
|
||||
return interval * time.Second
|
||||
}
|
||||
|
||||
const expMask = 0b111
|
||||
const mantBits = 4
|
||||
mant := interval & ((1 << mantBits) - 1)
|
||||
exp := (interval >> mantBits) & expMask
|
||||
return (mant | 0x10) << (exp + 3) * time.Second
|
||||
}
|
||||
|
||||
// AddressIterator is an iterator over IPv6 addresses.
|
||||
type AddressIterator struct {
|
||||
addressSize int
|
||||
buf *bytes.Buffer
|
||||
}
|
||||
|
||||
// Done indicates that the iterator has been exhausted/has no more elements.
|
||||
func (it *AddressIterator) Done() bool {
|
||||
return it.buf.Len() == 0
|
||||
}
|
||||
|
||||
// Next returns the next address in the iterator.
|
||||
//
|
||||
// Returns false if the iterator has been exhausted.
|
||||
func (it *AddressIterator) Next() (tcpip.Address, bool) {
|
||||
if it.Done() {
|
||||
var emptyAddress tcpip.Address
|
||||
return emptyAddress, false
|
||||
}
|
||||
|
||||
b := it.buf.Next(it.addressSize)
|
||||
if len(b) != it.addressSize {
|
||||
panic(fmt.Sprintf("got len(buf.Next(%d)) = %d, want = %d", it.addressSize, len(b), it.addressSize))
|
||||
}
|
||||
|
||||
return tcpip.Address(b), true
|
||||
}
|
||||
|
||||
func makeAddressIterator(b []byte, expectedAddresses uint16, addressSize int) (AddressIterator, bool) {
|
||||
expectedLen := int(expectedAddresses) * addressSize
|
||||
if len(b) < expectedLen {
|
||||
return AddressIterator{}, false
|
||||
}
|
||||
return AddressIterator{addressSize: addressSize, buf: bytes.NewBuffer(b[:expectedLen])}, true
|
||||
}
|
||||
Reference in New Issue
Block a user