356 lines
12 KiB
Go
356 lines
12 KiB
Go
// Copyright 2012 Google, Inc. All rights reserved.
|
|
// Copyright 2009-2011 Andreas Krennmair. 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"
|
|
"net"
|
|
"time"
|
|
|
|
"github.com/google/gopacket"
|
|
)
|
|
|
|
type IGMPType uint8
|
|
|
|
const (
|
|
IGMPMembershipQuery IGMPType = 0x11 // General or group specific query
|
|
IGMPMembershipReportV1 IGMPType = 0x12 // Version 1 Membership Report
|
|
IGMPMembershipReportV2 IGMPType = 0x16 // Version 2 Membership Report
|
|
IGMPLeaveGroup IGMPType = 0x17 // Leave Group
|
|
IGMPMembershipReportV3 IGMPType = 0x22 // Version 3 Membership Report
|
|
)
|
|
|
|
// String conversions for IGMP message types
|
|
func (i IGMPType) String() string {
|
|
switch i {
|
|
case IGMPMembershipQuery:
|
|
return "IGMP Membership Query"
|
|
case IGMPMembershipReportV1:
|
|
return "IGMPv1 Membership Report"
|
|
case IGMPMembershipReportV2:
|
|
return "IGMPv2 Membership Report"
|
|
case IGMPMembershipReportV3:
|
|
return "IGMPv3 Membership Report"
|
|
case IGMPLeaveGroup:
|
|
return "Leave Group"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
type IGMPv3GroupRecordType uint8
|
|
|
|
const (
|
|
IGMPIsIn IGMPv3GroupRecordType = 0x01 // Type MODE_IS_INCLUDE, source addresses x
|
|
IGMPIsEx IGMPv3GroupRecordType = 0x02 // Type MODE_IS_EXCLUDE, source addresses x
|
|
IGMPToIn IGMPv3GroupRecordType = 0x03 // Type CHANGE_TO_INCLUDE_MODE, source addresses x
|
|
IGMPToEx IGMPv3GroupRecordType = 0x04 // Type CHANGE_TO_EXCLUDE_MODE, source addresses x
|
|
IGMPAllow IGMPv3GroupRecordType = 0x05 // Type ALLOW_NEW_SOURCES, source addresses x
|
|
IGMPBlock IGMPv3GroupRecordType = 0x06 // Type BLOCK_OLD_SOURCES, source addresses x
|
|
)
|
|
|
|
func (i IGMPv3GroupRecordType) String() string {
|
|
switch i {
|
|
case IGMPIsIn:
|
|
return "MODE_IS_INCLUDE"
|
|
case IGMPIsEx:
|
|
return "MODE_IS_EXCLUDE"
|
|
case IGMPToIn:
|
|
return "CHANGE_TO_INCLUDE_MODE"
|
|
case IGMPToEx:
|
|
return "CHANGE_TO_EXCLUDE_MODE"
|
|
case IGMPAllow:
|
|
return "ALLOW_NEW_SOURCES"
|
|
case IGMPBlock:
|
|
return "BLOCK_OLD_SOURCES"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
// IGMP represents an IGMPv3 message.
|
|
type IGMP struct {
|
|
BaseLayer
|
|
Type IGMPType
|
|
MaxResponseTime time.Duration
|
|
Checksum uint16
|
|
GroupAddress net.IP
|
|
SupressRouterProcessing bool
|
|
RobustnessValue uint8
|
|
IntervalTime time.Duration
|
|
SourceAddresses []net.IP
|
|
NumberOfGroupRecords uint16
|
|
NumberOfSources uint16
|
|
GroupRecords []IGMPv3GroupRecord
|
|
Version uint8 // IGMP protocol version
|
|
}
|
|
|
|
// IGMPv1or2 stores header details for an IGMPv1 or IGMPv2 packet.
|
|
//
|
|
// 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 | Max Resp Time | Checksum |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | Group Address |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
type IGMPv1or2 struct {
|
|
BaseLayer
|
|
Type IGMPType // IGMP message type
|
|
MaxResponseTime time.Duration // meaningful only in Membership Query messages
|
|
Checksum uint16 // 16-bit checksum of entire ip payload
|
|
GroupAddress net.IP // either 0 or an IP multicast address
|
|
Version uint8
|
|
}
|
|
|
|
// decodeResponse dissects IGMPv1 or IGMPv2 packet.
|
|
func (i *IGMPv1or2) decodeResponse(data []byte) error {
|
|
if len(data) < 8 {
|
|
return errors.New("IGMP packet too small")
|
|
}
|
|
|
|
i.MaxResponseTime = igmpTimeDecode(data[1])
|
|
i.Checksum = binary.BigEndian.Uint16(data[2:4])
|
|
i.GroupAddress = net.IP(data[4:8])
|
|
|
|
return nil
|
|
}
|
|
|
|
// 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 = 0x22 | Reserved | Checksum |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | Reserved | Number of Group Records (M) |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | |
|
|
// . Group Record [1] .
|
|
// | |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | |
|
|
// . Group Record [2] .
|
|
// | |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | |
|
|
// . Group Record [M] .
|
|
// | |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | Record Type | Aux Data Len | Number of Sources (N) |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | Multicast Address |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | Source Address [1] |
|
|
// +- -+
|
|
// | Source Address [2] |
|
|
// +- -+
|
|
// | Source Address [N] |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
// | |
|
|
// . Auxiliary Data .
|
|
// | |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
|
|
// IGMPv3GroupRecord stores individual group records for a V3 Membership Report message.
|
|
type IGMPv3GroupRecord struct {
|
|
Type IGMPv3GroupRecordType
|
|
AuxDataLen uint8 // this should always be 0 as per IGMPv3 spec.
|
|
NumberOfSources uint16
|
|
MulticastAddress net.IP
|
|
SourceAddresses []net.IP
|
|
AuxData uint32 // NOT USED
|
|
}
|
|
|
|
func (i *IGMP) decodeIGMPv3MembershipReport(data []byte) error {
|
|
if len(data) < 8 {
|
|
return errors.New("IGMPv3 Membership Report too small #1")
|
|
}
|
|
|
|
i.Checksum = binary.BigEndian.Uint16(data[2:4])
|
|
i.NumberOfGroupRecords = binary.BigEndian.Uint16(data[6:8])
|
|
|
|
recordOffset := 8
|
|
for j := 0; j < int(i.NumberOfGroupRecords); j++ {
|
|
if len(data) < recordOffset+8 {
|
|
return errors.New("IGMPv3 Membership Report too small #2")
|
|
}
|
|
|
|
var gr IGMPv3GroupRecord
|
|
gr.Type = IGMPv3GroupRecordType(data[recordOffset])
|
|
gr.AuxDataLen = data[recordOffset+1]
|
|
gr.NumberOfSources = binary.BigEndian.Uint16(data[recordOffset+2 : recordOffset+4])
|
|
gr.MulticastAddress = net.IP(data[recordOffset+4 : recordOffset+8])
|
|
|
|
if len(data) < recordOffset+8+int(gr.NumberOfSources)*4 {
|
|
return errors.New("IGMPv3 Membership Report too small #3")
|
|
}
|
|
|
|
// append source address records.
|
|
for i := 0; i < int(gr.NumberOfSources); i++ {
|
|
sourceAddr := net.IP(data[recordOffset+8+i*4 : recordOffset+12+i*4])
|
|
gr.SourceAddresses = append(gr.SourceAddresses, sourceAddr)
|
|
}
|
|
|
|
i.GroupRecords = append(i.GroupRecords, gr)
|
|
recordOffset += 8 + 4*int(gr.NumberOfSources)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// 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] |
|
|
// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
//
|
|
// decodeIGMPv3MembershipQuery parses the IGMPv3 message of type 0x11
|
|
func (i *IGMP) decodeIGMPv3MembershipQuery(data []byte) error {
|
|
if len(data) < 12 {
|
|
return errors.New("IGMPv3 Membership Query too small #1")
|
|
}
|
|
|
|
i.MaxResponseTime = igmpTimeDecode(data[1])
|
|
i.Checksum = binary.BigEndian.Uint16(data[2:4])
|
|
i.SupressRouterProcessing = data[8]&0x8 != 0
|
|
i.GroupAddress = net.IP(data[4:8])
|
|
i.RobustnessValue = data[8] & 0x7
|
|
i.IntervalTime = igmpTimeDecode(data[9])
|
|
i.NumberOfSources = binary.BigEndian.Uint16(data[10:12])
|
|
|
|
if len(data) < 12+int(i.NumberOfSources)*4 {
|
|
return errors.New("IGMPv3 Membership Query too small #2")
|
|
}
|
|
|
|
for j := 0; j < int(i.NumberOfSources); j++ {
|
|
i.SourceAddresses = append(i.SourceAddresses, net.IP(data[12+j*4:16+j*4]))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// igmpTimeDecode decodes the duration created by the given byte, using the
|
|
// algorithm in http://www.rfc-base.org/txt/rfc-3376.txt section 4.1.1.
|
|
func igmpTimeDecode(t uint8) time.Duration {
|
|
if t&0x80 == 0 {
|
|
return time.Millisecond * 100 * time.Duration(t)
|
|
}
|
|
mant := (t & 0x70) >> 4
|
|
exp := t & 0x0F
|
|
return time.Millisecond * 100 * time.Duration((mant|0x10)<<(exp+3))
|
|
}
|
|
|
|
// LayerType returns LayerTypeIGMP for the V1,2,3 message protocol formats.
|
|
func (i *IGMP) LayerType() gopacket.LayerType { return LayerTypeIGMP }
|
|
func (i *IGMPv1or2) LayerType() gopacket.LayerType { return LayerTypeIGMP }
|
|
|
|
func (i *IGMPv1or2) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
|
|
if len(data) < 8 {
|
|
return errors.New("IGMP Packet too small")
|
|
}
|
|
|
|
i.Type = IGMPType(data[0])
|
|
i.MaxResponseTime = igmpTimeDecode(data[1])
|
|
i.Checksum = binary.BigEndian.Uint16(data[2:4])
|
|
i.GroupAddress = net.IP(data[4:8])
|
|
|
|
return nil
|
|
}
|
|
|
|
func (i *IGMPv1or2) NextLayerType() gopacket.LayerType {
|
|
return gopacket.LayerTypeZero
|
|
}
|
|
|
|
func (i *IGMPv1or2) CanDecode() gopacket.LayerClass {
|
|
return LayerTypeIGMP
|
|
}
|
|
|
|
// DecodeFromBytes decodes the given bytes into this layer.
|
|
func (i *IGMP) DecodeFromBytes(data []byte, df gopacket.DecodeFeedback) error {
|
|
if len(data) < 1 {
|
|
return errors.New("IGMP packet is too small")
|
|
}
|
|
|
|
// common IGMP header values between versions 1..3 of IGMP specification..
|
|
i.Type = IGMPType(data[0])
|
|
|
|
switch i.Type {
|
|
case IGMPMembershipQuery:
|
|
i.decodeIGMPv3MembershipQuery(data)
|
|
case IGMPMembershipReportV3:
|
|
i.decodeIGMPv3MembershipReport(data)
|
|
default:
|
|
return errors.New("unsupported IGMP type")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CanDecode returns the set of layer types that this DecodingLayer can decode.
|
|
func (i *IGMP) CanDecode() gopacket.LayerClass {
|
|
return LayerTypeIGMP
|
|
}
|
|
|
|
// NextLayerType returns the layer type contained by this DecodingLayer.
|
|
func (i *IGMP) NextLayerType() gopacket.LayerType {
|
|
return gopacket.LayerTypeZero
|
|
}
|
|
|
|
// decodeIGMP will parse IGMP v1,2 or 3 protocols. Checks against the
|
|
// IGMP type are performed against byte[0], logic then iniitalizes and
|
|
// passes the appropriate struct (IGMP or IGMPv1or2) to
|
|
// decodingLayerDecoder.
|
|
func decodeIGMP(data []byte, p gopacket.PacketBuilder) error {
|
|
if len(data) < 1 {
|
|
return errors.New("IGMP packet is too small")
|
|
}
|
|
|
|
// byte 0 contains IGMP message type.
|
|
switch IGMPType(data[0]) {
|
|
case IGMPMembershipQuery:
|
|
// IGMPv3 Membership Query payload is >= 12
|
|
if len(data) >= 12 {
|
|
i := &IGMP{Version: 3}
|
|
return decodingLayerDecoder(i, data, p)
|
|
} else if len(data) == 8 {
|
|
i := &IGMPv1or2{}
|
|
if data[1] == 0x00 {
|
|
i.Version = 1 // IGMPv1 has a query length of 8 and MaxResp = 0
|
|
} else {
|
|
i.Version = 2 // IGMPv2 has a query length of 8 and MaxResp != 0
|
|
}
|
|
|
|
return decodingLayerDecoder(i, data, p)
|
|
}
|
|
case IGMPMembershipReportV3:
|
|
i := &IGMP{Version: 3}
|
|
return decodingLayerDecoder(i, data, p)
|
|
case IGMPMembershipReportV1:
|
|
i := &IGMPv1or2{Version: 1}
|
|
return decodingLayerDecoder(i, data, p)
|
|
case IGMPLeaveGroup, IGMPMembershipReportV2:
|
|
// leave group and Query Report v2 used in IGMPv2 only.
|
|
i := &IGMPv1or2{Version: 2}
|
|
return decodingLayerDecoder(i, data, p)
|
|
default:
|
|
}
|
|
|
|
return errors.New("Unable to determine IGMP type.")
|
|
}
|