| // 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.") |
| } |