diff --git a/pkg/tcpip/header/BUILD b/pkg/tcpip/header/BUILD index 6278dbcc9..8119f6ee6 100644 --- a/pkg/tcpip/header/BUILD +++ b/pkg/tcpip/header/BUILD @@ -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", diff --git a/pkg/tcpip/header/igmp.go b/pkg/tcpip/header/igmp.go index 94c057f24..af7726c3e 100644 --- a/pkg/tcpip/header/igmp.go +++ b/pkg/tcpip/header/igmp.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. diff --git a/pkg/tcpip/header/igmp_test.go b/pkg/tcpip/header/igmp_test.go index 229eb477a..aab54c9f7 100644 --- a/pkg/tcpip/header/igmp_test.go +++ b/pkg/tcpip/header/igmp_test.go @@ -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<=32768, Maximum Response Code represents a +// floating-point value as follows: +// +// 0 1 2 3 4 5 6 7 8 9 A B C D E F +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// |1| exp | mant | +// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +// +// Maximum Response Delay = (mant | 0x1000) << (exp+3) +// +// Small values of Maximum Response Delay allow MLDv2 routers to tune +// the "leave latency" (the time between the moment the last node on a +// link ceases to listen to a specific multicast address and the moment +// the routing protocol is notified that there are no more listeners for +// that address). Larger values, especially in the exponential range, +// allow the tuning of the burstiness of MLD traffic on a link. +func MLDv2MaximumResponseDelay(codeRaw uint16) time.Duration { + code := time.Duration(codeRaw) + if code < 32768 { + return code * time.Millisecond + } + + const mantBits = 12 + const expMask = 0b111 + exp := (code >> mantBits) & expMask + mant := code & ((1 << mantBits) - 1) + return (mant | 0x1000) << (exp + 3) * time.Millisecond + +} + +// MulticastAddress returns the Multicast Address. +func (m MLDv2Query) MulticastAddress() tcpip.Address { + // As per RFC 2710 section 3.5: + // + // In a Query message, the Multicast Address field is set to zero when + // sending a General Query, and set to a specific IPv6 multicast address + // when sending a Multicast-Address-Specific Query. + // + // In a Report or Done message, the Multicast Address field holds a + // specific IPv6 multicast address to which the message sender is + // listening or is ceasing to listen, respectively. + return tcpip.Address(m[mldMulticastAddressOffset:][:IPv6AddressSize]) +} + +// QuerierRobustnessVariable returns the querier's robustness variable. +func (m MLDv2Query) QuerierRobustnessVariable() uint8 { + return m[mldv2QueryResvSQRVOffset] & mldv2QueryQRVMask +} + +// QuerierQueryInterval returns the querier's query interval. +func (m MLDv2Query) QuerierQueryInterval() time.Duration { + return mldv2AndIGMPv3QuerierQueryCodeToInterval(m[mldv2QueryQQICOffset]) +} + +// Sources returns an iterator over source addresses in the query. +// +// Returns false if the message cannot hold the expected number of sources. +func (m MLDv2Query) Sources() (AddressIterator, bool) { + return makeAddressIterator( + m[mldv2QuerySourcesOffset:], + binary.BigEndian.Uint16(m[mldv2QueryNumberOfSourcesOffset:]), + IPv6AddressSize, + ) +} + +// MLDv2ReportRecordType is the type of an MLDv2 multicast address record +// found in an MLDv2 report, as per RFC 3810 section 5.2.12. +type MLDv2ReportRecordType int + +// MLDv2 multicast address record types, as per RFC 3810 section 5.2.12. +const ( + MLDv2ReportRecordModeIsInclude MLDv2ReportRecordType = 1 + MLDv2ReportRecordModeIsExclude MLDv2ReportRecordType = 2 + MLDv2ReportRecordChangeToIncludeMode MLDv2ReportRecordType = 3 + MLDv2ReportRecordChangeToExcludeMode MLDv2ReportRecordType = 4 + MLDv2ReportRecordAllowNewSources MLDv2ReportRecordType = 5 + MLDv2ReportRecordBlockOldSources MLDv2ReportRecordType = 6 +) + +const ( + mldv2ReportMulticastAddressRecordMinimumSize = 20 + mldv2ReportMulticastAddressRecordTypeOffset = 0 + mldv2ReportMulticastAddressRecordAuxDataLenOffset = 1 + mldv2ReportMulticastAddressRecordAuxDataLenUnits = 4 + mldv2ReportMulticastAddressRecordNumberOfSourcesOffset = 2 + mldv2ReportMulticastAddressRecordMulticastAddressOffset = 4 + mldv2ReportMulticastAddressRecordSourcesOffset = 20 +) + +// MLDv2ReportMulticastAddressRecordSerializer is an MLDv2 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 MLDv2ReportMulticastAddressRecordSerializer struct { + RecordType MLDv2ReportRecordType + MulticastAddress tcpip.Address + Sources []tcpip.Address +} + +// Length returns the number of bytes this serializer would occupy. +func (s *MLDv2ReportMulticastAddressRecordSerializer) Length() int { + return mldv2ReportMulticastAddressRecordSourcesOffset + len(s.Sources)*IPv6AddressSize +} + +func copyIPv6Address(dst []byte, src tcpip.Address) { + if n := copy(dst, src); n != IPv6AddressSize { + panic(fmt.Sprintf("got copy(...) = %d, want = %d", n, IPv6AddressSize)) + } +} + +// SerializeInto serializes the record into the buffer. +// +// Panics if the buffer does not have enough space to fit the record. +func (s *MLDv2ReportMulticastAddressRecordSerializer) SerializeInto(b []byte) { + b[mldv2ReportMulticastAddressRecordTypeOffset] = byte(s.RecordType) + b[mldv2ReportMulticastAddressRecordAuxDataLenOffset] = 0 + binary.BigEndian.PutUint16(b[mldv2ReportMulticastAddressRecordNumberOfSourcesOffset:], uint16(len(s.Sources))) + copyIPv6Address(b[mldv2ReportMulticastAddressRecordMulticastAddressOffset:], s.MulticastAddress) + b = b[mldv2ReportMulticastAddressRecordSourcesOffset:] + for _, source := range s.Sources { + copyIPv6Address(b, source) + b = b[IPv6AddressSize:] + } +} + +const ( + mldv2ReportReservedOffset = 0 + mldv2ReportNumberOfMulticastAddressRecordsOffset = 2 + mldv2ReportMulticastAddressRecordsOffset = 4 +) + +// MLDv2ReportSerializer 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 MLDv2ReportSerializer struct { + Records []MLDv2ReportMulticastAddressRecordSerializer +} + +// Length returns the number of bytes this serializer would occupy. +func (s *MLDv2ReportSerializer) Length() int { + ret := mldv2ReportMulticastAddressRecordsOffset + 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 *MLDv2ReportSerializer) SerializeInto(b []byte) { + binary.BigEndian.PutUint16(b[mldv2ReportReservedOffset:], 0) + binary.BigEndian.PutUint16(b[mldv2ReportNumberOfMulticastAddressRecordsOffset:], uint16(len(s.Records))) + b = b[mldv2ReportMulticastAddressRecordsOffset:] + for _, record := range s.Records { + len := record.Length() + record.SerializeInto(b[:len]) + b = b[len:] + } +} + +// MLDv2ReportMulticastAddressRecord is an MLDv2 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 MLDv2ReportMulticastAddressRecord []byte + +// RecordType returns the type of this record. +func (r MLDv2ReportMulticastAddressRecord) RecordType() MLDv2ReportRecordType { + return MLDv2ReportRecordType(r[mldv2ReportMulticastAddressRecordTypeOffset]) +} + +// AuxDataLen returns the length of the auxillary data in this record. +func (r MLDv2ReportMulticastAddressRecord) AuxDataLen() int { + return int(r[mldv2ReportMulticastAddressRecordAuxDataLenOffset]) * mldv2ReportMulticastAddressRecordAuxDataLenUnits +} + +// numberOfSources returns the number of sources in this record. +func (r MLDv2ReportMulticastAddressRecord) numberOfSources() uint16 { + return binary.BigEndian.Uint16(r[mldv2ReportMulticastAddressRecordNumberOfSourcesOffset:]) +} + +// MulticastAddress returns the multicast address this record targets. +func (r MLDv2ReportMulticastAddressRecord) MulticastAddress() tcpip.Address { + return tcpip.Address(r[mldv2ReportMulticastAddressRecordMulticastAddressOffset:][:IPv6AddressSize]) +} + +// Sources returns an iterator over source addresses in the query. +// +// Returns false if the message cannot hold the expected number of sources. +func (r MLDv2ReportMulticastAddressRecord) Sources() (AddressIterator, bool) { + expectedLen := int(r.numberOfSources()) * IPv6AddressSize + b := r[mldv2ReportMulticastAddressRecordSourcesOffset:] + if len(b) < expectedLen { + return AddressIterator{}, false + } + return AddressIterator{addressSize: IPv6AddressSize, buf: bytes.NewBuffer(b[:expectedLen])}, true +} + +// MLDv2Report is an MLDv2 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 MLDv2Report []byte + +// MLDv2ReportMulticastAddressRecordIterator is an iterator over MLDv2 Multicast +// Address Records. +type MLDv2ReportMulticastAddressRecordIterator struct { + recordsLeft uint16 + buf *bytes.Buffer +} + +// MLDv2ReportMulticastAddressRecordIteratorNextDisposition is the possible +// return values from MLDv2ReportMulticastAddressRecordIterator.Next. +type MLDv2ReportMulticastAddressRecordIteratorNextDisposition int + +const ( + // MLDv2ReportMulticastAddressRecordIteratorNextOk indicates that a multicast + // address record was yielded. + MLDv2ReportMulticastAddressRecordIteratorNextOk MLDv2ReportMulticastAddressRecordIteratorNextDisposition = iota + + // MLDv2ReportMulticastAddressRecordIteratorNextDone indicates that the iterator + // has been exhausted. + MLDv2ReportMulticastAddressRecordIteratorNextDone + + // MLDv2ReportMulticastAddressRecordIteratorNextErrBufferTooShort indicates + // that the iterator expected another record, but the buffer ended + // prematurely. + MLDv2ReportMulticastAddressRecordIteratorNextErrBufferTooShort +) + +// Next returns the next MLDv2 Multicast Address Record. +func (it *MLDv2ReportMulticastAddressRecordIterator) Next() (MLDv2ReportMulticastAddressRecord, MLDv2ReportMulticastAddressRecordIteratorNextDisposition) { + if it.recordsLeft == 0 { + return MLDv2ReportMulticastAddressRecord{}, MLDv2ReportMulticastAddressRecordIteratorNextDone + } + if it.buf.Len() < mldv2ReportMulticastAddressRecordMinimumSize { + return MLDv2ReportMulticastAddressRecord{}, MLDv2ReportMulticastAddressRecordIteratorNextErrBufferTooShort + } + + hdr := MLDv2ReportMulticastAddressRecord(it.buf.Bytes()) + expectedLen := mldv2ReportMulticastAddressRecordMinimumSize + + int(hdr.AuxDataLen()) + int(hdr.numberOfSources())*IPv6AddressSize + + bytes := it.buf.Next(expectedLen) + if len(bytes) < expectedLen { + return MLDv2ReportMulticastAddressRecord{}, MLDv2ReportMulticastAddressRecordIteratorNextErrBufferTooShort + } + it.recordsLeft-- + return MLDv2ReportMulticastAddressRecord(bytes), MLDv2ReportMulticastAddressRecordIteratorNextOk +} + +// MulticastAddressRecords returns an iterator of MLDv2 Multicast Address +// Records. +func (m MLDv2Report) MulticastAddressRecords() MLDv2ReportMulticastAddressRecordIterator { + return MLDv2ReportMulticastAddressRecordIterator{ + recordsLeft: binary.BigEndian.Uint16(m[mldv2ReportNumberOfMulticastAddressRecordsOffset:]), + buf: bytes.NewBuffer(m[mldv2ReportMulticastAddressRecordsOffset:]), + } +} diff --git a/pkg/tcpip/header/mldv2_igmpv3_common.go b/pkg/tcpip/header/mldv2_igmpv3_common.go new file mode 100644 index 000000000..cef2436c7 --- /dev/null +++ b/pkg/tcpip/header/mldv2_igmpv3_common.go @@ -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 +}