123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- // Copyright 2018 GoPacket Authors. All rights reserved.
- //
- // Use of this source code is governed by a BSD-style license
- // that can be found in the LICENSE file in the root of the source
- // tree.
- package layers
- import (
- "encoding/binary"
- "errors"
- "fmt"
- "math"
- "net"
- "time"
- "github.com/google/gopacket"
- )
- const (
- // S Flag bit is 1
- mldv2STrue uint8 = 0x8
- // S Flag value mask
- // mldv2STrue & mldv2SMask == mldv2STrue // true
- // 0x1 & mldv2SMask == mldv2STrue // true
- // 0x0 & mldv2SMask == mldv2STrue // false
- mldv2SMask uint8 = 0x8
- // QRV value mask
- mldv2QRVMask uint8 = 0x7
- )
- // MLDv2MulticastListenerQueryMessage are sent by multicast routers to query the
- // multicast listening state of neighboring interfaces.
- // https://tools.ietf.org/html/rfc3810#section-5.1
- //
- // Some information, like Maximum Response Code and Multicast Address are in the
- // previous layer LayerTypeMLDv1MulticastListenerQuery
- type MLDv2MulticastListenerQueryMessage struct {
- BaseLayer
- // 5.1.3. Maximum Response Delay COde
- MaximumResponseCode uint16
- // 5.1.5. Multicast Address
- // Zero in general query
- // Specific IPv6 multicast address otherwise
- MulticastAddress net.IP
- // 5.1.7. S Flag (Suppress Router-Side Processing)
- SuppressRoutersideProcessing bool
- // 5.1.8. QRV (Querier's Robustness Variable)
- QueriersRobustnessVariable uint8
- // 5.1.9. QQIC (Querier's Query Interval Code)
- QueriersQueryIntervalCode uint8
- // 5.1.10. Number of Sources (N)
- NumberOfSources uint16
- // 5.1.11 Source Address [i]
- SourceAddresses []net.IP
- }
- // DecodeFromBytes decodes the given bytes into this layer.
- func (m *MLDv2MulticastListenerQueryMessage) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
- if len(data) < 24 {
- df.SetTruncated()
- return errors.New("ICMP layer less than 24 bytes for Multicast Listener Query Message V2")
- }
- m.MaximumResponseCode = binary.BigEndian.Uint16(data[0:2])
- // ignore data[2:4] as per https://tools.ietf.org/html/rfc3810#section-5.1.4
- m.MulticastAddress = data[4:20]
- m.SuppressRoutersideProcessing = (data[20] & mldv2SMask) == mldv2STrue
- m.QueriersRobustnessVariable = data[20] & mldv2QRVMask
- m.QueriersQueryIntervalCode = data[21]
- m.NumberOfSources = binary.BigEndian.Uint16(data[22:24])
- var end int
- for i := uint16(0); i < m.NumberOfSources; i++ {
- begin := 24 + (int(i) * 16)
- end = begin + 16
- if end > len(data) {
- df.SetTruncated()
- return fmt.Errorf("ICMP layer less than %d bytes for Multicast Listener Query Message V2", end)
- }
- m.SourceAddresses = append(m.SourceAddresses, data[begin:end])
- }
- return nil
- }
- // NextLayerType returns the layer type contained by this DecodingLayer.
- func (*MLDv2MulticastListenerQueryMessage) NextLayerType() gopacket.LayerType {
- return gopacket.LayerTypeZero
- }
- // SerializeTo writes the serialized form of this layer into the
- // SerializationBuffer, implementing gopacket.SerializableLayer.
- // See the docs for gopacket.SerializableLayer for more info.
- func (m *MLDv2MulticastListenerQueryMessage) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
- if err := m.serializeSourceAddressesTo(b, opts); err != nil {
- return err
- }
- buf, err := b.PrependBytes(24)
- if err != nil {
- return err
- }
- binary.BigEndian.PutUint16(buf[0:2], m.MaximumResponseCode)
- copy(buf[2:4], []byte{0x00, 0x00}) // set reserved bytes to zero
- ma16 := m.MulticastAddress.To16()
- if ma16 == nil {
- return fmt.Errorf("invalid MulticastAddress '%s'", m.MulticastAddress)
- }
- copy(buf[4:20], ma16)
- byte20 := m.QueriersRobustnessVariable & mldv2QRVMask
- if m.SuppressRoutersideProcessing {
- byte20 |= mldv2STrue
- } else {
- byte20 &= ^mldv2STrue // the complement of mldv2STrue
- }
- byte20 &= 0x0F // set reserved bits to zero
- buf[20] = byte20
- binary.BigEndian.PutUint16(buf[22:24], m.NumberOfSources)
- buf[21] = m.QueriersQueryIntervalCode
- return nil
- }
- // writes each source address to the buffer preserving the order
- func (m *MLDv2MulticastListenerQueryMessage) serializeSourceAddressesTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
- numberOfSourceAddresses := len(m.SourceAddresses)
- if numberOfSourceAddresses > math.MaxUint16 {
- return fmt.Errorf(
- "there are more than %d source addresses, but 65535 is the maximum number of supported addresses",
- numberOfSourceAddresses)
- }
- if opts.FixLengths {
- m.NumberOfSources = uint16(numberOfSourceAddresses)
- }
- lastSAIdx := numberOfSourceAddresses - 1
- for k := range m.SourceAddresses {
- i := lastSAIdx - k // reverse order
- buf, err := b.PrependBytes(16)
- if err != nil {
- return err
- }
- sa16 := m.SourceAddresses[i].To16()
- if sa16 == nil {
- return fmt.Errorf("invalid source address [%d] '%s'", i, m.SourceAddresses[i])
- }
- copy(buf[0:16], sa16)
- }
- return nil
- }
- // String sums this layer up nicely formatted
- func (m *MLDv2MulticastListenerQueryMessage) String() string {
- return fmt.Sprintf(
- "Maximum Response Code: %#x (%dms), Multicast Address: %s, Suppress Routerside Processing: %t, QRV: %#x, QQIC: %#x (%ds), Number of Source Address: %d (actual: %d), Source Addresses: %s",
- m.MaximumResponseCode,
- m.MaximumResponseDelay(),
- m.MulticastAddress,
- m.SuppressRoutersideProcessing,
- m.QueriersRobustnessVariable,
- m.QueriersQueryIntervalCode,
- m.QQI()/time.Second,
- m.NumberOfSources,
- len(m.SourceAddresses),
- m.SourceAddresses)
- }
- // LayerType returns LayerTypeMLDv2MulticastListenerQuery.
- func (*MLDv2MulticastListenerQueryMessage) LayerType() gopacket.LayerType {
- return LayerTypeMLDv2MulticastListenerQuery
- }
- // CanDecode returns the set of layer types that this DecodingLayer can decode.
- func (*MLDv2MulticastListenerQueryMessage) CanDecode() gopacket.LayerClass {
- return LayerTypeMLDv2MulticastListenerQuery
- }
- // QQI calculates the Querier's Query Interval based on the QQIC
- // according to https://tools.ietf.org/html/rfc3810#section-5.1.9
- func (m *MLDv2MulticastListenerQueryMessage) QQI() time.Duration {
- data := m.QueriersQueryIntervalCode
- if data < 128 {
- return time.Second * time.Duration(data)
- }
- exp := uint16(data) & 0x70 >> 4
- mant := uint16(data) & 0x0F
- return time.Second * time.Duration(mant|0x1000<<(exp+3))
- }
- // SetQQI calculates and updates the Querier's Query Interval Code (QQIC)
- // according to https://tools.ietf.org/html/rfc3810#section-5.1.9
- func (m *MLDv2MulticastListenerQueryMessage) SetQQI(d time.Duration) error {
- if d < 0 {
- m.QueriersQueryIntervalCode = 0
- return errors.New("QQI duration is negative")
- }
- if d == 0 {
- m.QueriersQueryIntervalCode = 0
- return nil
- }
- dms := d / time.Second
- if dms < 128 {
- m.QueriersQueryIntervalCode = uint8(dms)
- }
- if dms > 31744 { // mant=0xF, exp=0x7
- m.QueriersQueryIntervalCode = 0xFF
- return fmt.Errorf("QQI duration %ds is, maximum allowed is 31744s", dms)
- }
- value := uint16(dms) // ok, because 31744 < math.MaxUint16
- exp := uint8(7)
- for mask := uint16(0x4000); exp > 0; exp-- {
- if mask&value != 0 {
- break
- }
- mask >>= 1
- }
- mant := uint8(0x000F & (value >> (exp + 3)))
- sig := uint8(0x10)
- m.QueriersQueryIntervalCode = sig | exp<<4 | mant
- return nil
- }
- // MaximumResponseDelay returns the Maximum Response Delay based on the
- // Maximum Response Code according to
- // https://tools.ietf.org/html/rfc3810#section-5.1.3
- func (m *MLDv2MulticastListenerQueryMessage) MaximumResponseDelay() time.Duration {
- if m.MaximumResponseCode < 0x8000 {
- return time.Duration(m.MaximumResponseCode)
- }
- exp := m.MaximumResponseCode & 0x7000 >> 12
- mant := m.MaximumResponseCode & 0x0FFF
- return time.Millisecond * time.Duration(mant|0x1000<<(exp+3))
- }
- // SetMLDv2MaximumResponseDelay updates the Maximum Response Code according to
- // https://tools.ietf.org/html/rfc3810#section-5.1.3
- func (m *MLDv2MulticastListenerQueryMessage) SetMLDv2MaximumResponseDelay(d time.Duration) error {
- if d == 0 {
- m.MaximumResponseCode = 0
- return nil
- }
- if d < 0 {
- return errors.New("maximum response delay must not be negative")
- }
- dms := d / time.Millisecond
- if dms < 32768 {
- m.MaximumResponseCode = uint16(dms)
- }
- if dms > 4193280 { // mant=0xFFF, exp=0x7
- return fmt.Errorf("maximum response delay %dms is bigger the than maximum of 4193280ms", dms)
- }
- value := uint32(dms) // ok, because 4193280 < math.MaxUint32
- exp := uint8(7)
- for mask := uint32(0x40000000); exp > 0; exp-- {
- if mask&value != 0 {
- break
- }
- mask >>= 1
- }
- mant := uint16(0x00000FFF & (value >> (exp + 3)))
- sig := uint16(0x1000)
- m.MaximumResponseCode = sig | uint16(exp)<<12 | mant
- return nil
- }
- // MLDv2MulticastListenerReportMessage is sent by an IP node to report the
- // current multicast listening state, or changes therein.
- // https://tools.ietf.org/html/rfc3810#section-5.2
- type MLDv2MulticastListenerReportMessage struct {
- BaseLayer
- // 5.2.3. Nr of Mcast Address Records
- NumberOfMulticastAddressRecords uint16
- // 5.2.4. Multicast Address Record [i]
- MulticastAddressRecords []MLDv2MulticastAddressRecord
- }
- // DecodeFromBytes decodes the given bytes into this layer.
- func (m *MLDv2MulticastListenerReportMessage) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
- if len(data) < 4 {
- df.SetTruncated()
- return errors.New("ICMP layer less than 4 bytes for Multicast Listener Report Message V2")
- }
- // ignore data[0:2] as per RFC
- // https://tools.ietf.org/html/rfc3810#section-5.2.1
- m.NumberOfMulticastAddressRecords = binary.BigEndian.Uint16(data[2:4])
- begin := 4
- for i := uint16(0); i < m.NumberOfMulticastAddressRecords; i++ {
- mar := MLDv2MulticastAddressRecord{}
- read, err := mar.decode(data[begin:], df)
- if err != nil {
- return err
- }
- m.MulticastAddressRecords = append(m.MulticastAddressRecords, mar)
- begin += read
- }
- return nil
- }
- // SerializeTo writes the serialized form of this layer into the
- // SerializationBuffer, implementing gopacket.SerializableLayer.
- // See the docs for gopacket.SerializableLayer for more info.
- func (m *MLDv2MulticastListenerReportMessage) SerializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
- lastItemIdx := len(m.MulticastAddressRecords) - 1
- for k := range m.MulticastAddressRecords {
- i := lastItemIdx - k // reverse order
- err := m.MulticastAddressRecords[i].serializeTo(b, opts)
- if err != nil {
- return err
- }
- }
- if opts.FixLengths {
- numberOfMAR := len(m.MulticastAddressRecords)
- if numberOfMAR > math.MaxUint16 {
- return fmt.Errorf(
- "%d multicast address records added, but the maximum is 65535",
- numberOfMAR)
- }
- m.NumberOfMulticastAddressRecords = uint16(numberOfMAR)
- }
- buf, err := b.PrependBytes(4)
- if err != nil {
- return err
- }
- copy(buf[0:2], []byte{0x0, 0x0})
- binary.BigEndian.PutUint16(buf[2:4], m.NumberOfMulticastAddressRecords)
- return nil
- }
- // Sums this layer up nicely formatted
- func (m *MLDv2MulticastListenerReportMessage) String() string {
- return fmt.Sprintf(
- "Number of Mcast Addr Records: %d (actual %d), Multicast Address Records: %+v",
- m.NumberOfMulticastAddressRecords,
- len(m.MulticastAddressRecords),
- m.MulticastAddressRecords)
- }
- // LayerType returns LayerTypeMLDv2MulticastListenerQuery.
- func (*MLDv2MulticastListenerReportMessage) LayerType() gopacket.LayerType {
- return LayerTypeMLDv2MulticastListenerReport
- }
- // CanDecode returns the set of layer types that this DecodingLayer can decode.
- func (*MLDv2MulticastListenerReportMessage) CanDecode() gopacket.LayerClass {
- return LayerTypeMLDv2MulticastListenerReport
- }
- // NextLayerType returns the layer type contained by this DecodingLayer.
- func (*MLDv2MulticastListenerReportMessage) NextLayerType() gopacket.LayerType {
- return gopacket.LayerTypePayload
- }
- // MLDv2MulticastAddressRecordType holds the type of a
- // Multicast Address Record, according to
- // https://tools.ietf.org/html/rfc3810#section-5.2.5 and
- // https://tools.ietf.org/html/rfc3810#section-5.2.12
- type MLDv2MulticastAddressRecordType uint8
- const (
- // MLDv2MulticastAddressRecordTypeModeIsIncluded stands for
- // MODE_IS_INCLUDE - indicates that the interface has a filter
- // mode of INCLUDE for the specified multicast address.
- MLDv2MulticastAddressRecordTypeModeIsIncluded MLDv2MulticastAddressRecordType = 1
- // MLDv2MulticastAddressRecordTypeModeIsExcluded stands for
- // MODE_IS_EXCLUDE - indicates that the interface has a filter
- // mode of EXCLUDE for the specified multicast address.
- MLDv2MulticastAddressRecordTypeModeIsExcluded MLDv2MulticastAddressRecordType = 2
- // MLDv2MulticastAddressRecordTypeChangeToIncludeMode stands for
- // CHANGE_TO_INCLUDE_MODE - indicates that the interface has
- // changed to INCLUDE filter mode for the specified multicast
- // address.
- MLDv2MulticastAddressRecordTypeChangeToIncludeMode MLDv2MulticastAddressRecordType = 3
- // MLDv2MulticastAddressRecordTypeChangeToExcludeMode stands for
- // CHANGE_TO_EXCLUDE_MODE - indicates that the interface has
- // changed to EXCLUDE filter mode for the specified multicast
- // address
- MLDv2MulticastAddressRecordTypeChangeToExcludeMode MLDv2MulticastAddressRecordType = 4
- // MLDv2MulticastAddressRecordTypeAllowNewSources stands for
- // ALLOW_NEW_SOURCES - indicates that the Source Address [i]
- // fields in this Multicast Address Record contain a list of
- // the additional sources that the node wishes to listen to,
- // for packets sent to the specified multicast address.
- MLDv2MulticastAddressRecordTypeAllowNewSources MLDv2MulticastAddressRecordType = 5
- // MLDv2MulticastAddressRecordTypeBlockOldSources stands for
- // BLOCK_OLD_SOURCES - indicates that the Source Address [i]
- // fields in this Multicast Address Record contain a list of
- // the sources that the node no longer wishes to listen to,
- // for packets sent to the specified multicast address.
- MLDv2MulticastAddressRecordTypeBlockOldSources MLDv2MulticastAddressRecordType = 6
- )
- // Human readable record types
- // Naming follows https://tools.ietf.org/html/rfc3810#section-5.2.12
- func (m MLDv2MulticastAddressRecordType) String() string {
- switch m {
- case MLDv2MulticastAddressRecordTypeModeIsIncluded:
- return "MODE_IS_INCLUDE"
- case MLDv2MulticastAddressRecordTypeModeIsExcluded:
- return "MODE_IS_EXCLUDE"
- case MLDv2MulticastAddressRecordTypeChangeToIncludeMode:
- return "CHANGE_TO_INCLUDE_MODE"
- case MLDv2MulticastAddressRecordTypeChangeToExcludeMode:
- return "CHANGE_TO_EXCLUDE_MODE"
- case MLDv2MulticastAddressRecordTypeAllowNewSources:
- return "ALLOW_NEW_SOURCES"
- case MLDv2MulticastAddressRecordTypeBlockOldSources:
- return "BLOCK_OLD_SOURCES"
- default:
- return fmt.Sprintf("UNKNOWN(%d)", m)
- }
- }
- // MLDv2MulticastAddressRecord contains information on the sender listening to a
- // single multicast address on the interface the report is sent.
- // https://tools.ietf.org/html/rfc3810#section-5.2.4
- type MLDv2MulticastAddressRecord struct {
- // 5.2.5. Record Type
- RecordType MLDv2MulticastAddressRecordType
- // 5.2.6. Auxiliary Data Length (number of 32-bit words)
- AuxDataLen uint8
- // 5.2.7. Number Of Sources (N)
- N uint16
- // 5.2.8. Multicast Address
- MulticastAddress net.IP
- // 5.2.9 Source Address [i]
- SourceAddresses []net.IP
- // 5.2.10 Auxiliary Data
- AuxiliaryData []byte
- }
- // decodes a multicast address record from bytes
- func (m *MLDv2MulticastAddressRecord) decode(data []byte, df gopacket.DecodeFeedback) (int, error) {
- if len(data) < 4 {
- df.SetTruncated()
- return 0, errors.New(
- "Multicast Listener Report Message V2 layer less than 4 bytes for Multicast Address Record")
- }
- m.RecordType = MLDv2MulticastAddressRecordType(data[0])
- m.AuxDataLen = data[1]
- m.N = binary.BigEndian.Uint16(data[2:4])
- m.MulticastAddress = data[4:20]
- for i := uint16(0); i < m.N; i++ {
- begin := 20 + (int(i) * 16)
- end := begin + 16
- if len(data) < end {
- df.SetTruncated()
- return begin, fmt.Errorf(
- "Multicast Listener Report Message V2 layer less than %d bytes for Multicast Address Record", end)
- }
- m.SourceAddresses = append(m.SourceAddresses, data[begin:end])
- }
- expectedLengthWithouAuxData := 20 + (int(m.N) * 16)
- expectedTotalLength := (int(m.AuxDataLen) * 4) + expectedLengthWithouAuxData // *4 because AuxDataLen are 32bit words
- if len(data) < expectedTotalLength {
- return expectedLengthWithouAuxData, fmt.Errorf(
- "Multicast Listener Report Message V2 layer less than %d bytes for Multicast Address Record",
- expectedLengthWithouAuxData)
- }
- m.AuxiliaryData = data[expectedLengthWithouAuxData:expectedTotalLength]
- return expectedTotalLength, nil
- }
- // String sums this layer up nicely formatted
- func (m *MLDv2MulticastAddressRecord) String() string {
- return fmt.Sprintf(
- "RecordType: %d (%s), AuxDataLen: %d [32-bit words], N: %d, Multicast Address: %s, SourceAddresses: %s, Auxiliary Data: %#x",
- m.RecordType,
- m.RecordType.String(),
- m.AuxDataLen,
- m.N,
- m.MulticastAddress.To16(),
- m.SourceAddresses,
- m.AuxiliaryData)
- }
- // serializes a multicast address record
- func (m *MLDv2MulticastAddressRecord) serializeTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
- if err := m.serializeAuxiliaryDataTo(b, opts); err != nil {
- return err
- }
- if err := m.serializeSourceAddressesTo(b, opts); err != nil {
- return err
- }
- buf, err := b.PrependBytes(20)
- if err != nil {
- return err
- }
- buf[0] = uint8(m.RecordType)
- buf[1] = m.AuxDataLen
- binary.BigEndian.PutUint16(buf[2:4], m.N)
- ma16 := m.MulticastAddress.To16()
- if ma16 == nil {
- return fmt.Errorf("invalid multicast address '%s'", m.MulticastAddress)
- }
- copy(buf[4:20], ma16)
- return nil
- }
- // serializes the auxiliary data of a multicast address record
- func (m *MLDv2MulticastAddressRecord) serializeAuxiliaryDataTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
- if remainder := len(m.AuxiliaryData) % 4; remainder != 0 {
- zeroWord := []byte{0x0, 0x0, 0x0, 0x0}
- m.AuxiliaryData = append(m.AuxiliaryData, zeroWord[:remainder]...)
- }
- if opts.FixLengths {
- auxDataLen := len(m.AuxiliaryData) / 4
- if auxDataLen > math.MaxUint8 {
- return fmt.Errorf("auxilary data is %d 32-bit words, but the maximum is 255 32-bit words", auxDataLen)
- }
- m.AuxDataLen = uint8(auxDataLen)
- }
- buf, err := b.PrependBytes(len(m.AuxiliaryData))
- if err != nil {
- return err
- }
- copy(buf, m.AuxiliaryData)
- return nil
- }
- // serializes the source addresses of a multicast address record preserving the order
- func (m *MLDv2MulticastAddressRecord) serializeSourceAddressesTo(b gopacket.SerializeBuffer, opts gopacket.SerializeOptions) error {
- if opts.FixLengths {
- numberOfSourceAddresses := len(m.SourceAddresses)
- if numberOfSourceAddresses > math.MaxUint16 {
- return fmt.Errorf(
- "%d source addresses added, but the maximum is 65535",
- numberOfSourceAddresses)
- }
- m.N = uint16(numberOfSourceAddresses)
- }
- lastItemIdx := len(m.SourceAddresses) - 1
- for k := range m.SourceAddresses {
- i := lastItemIdx - k // reverse order
- buf, err := b.PrependBytes(16)
- if err != nil {
- return err
- }
- sa16 := m.SourceAddresses[i].To16()
- if sa16 == nil {
- return fmt.Errorf("invalid source address [%d] '%s'", i, m.SourceAddresses[i])
- }
- copy(buf, sa16)
- }
- return nil
- }
- func decodeMLDv2MulticastListenerReport(data []byte, p gopacket.PacketBuilder) error {
- m := &MLDv2MulticastListenerReportMessage{}
- return decodingLayerDecoder(m, data, p)
- }
- func decodeMLDv2MulticastListenerQuery(data []byte, p gopacket.PacketBuilder) error {
- m := &MLDv2MulticastListenerQueryMessage{}
- return decodingLayerDecoder(m, data, p)
- }
|