blob: 3573d6ce79e232548de2650a0f2687832c97ddd4 [file] [log] [blame]
Chetan Gaonkercb122cc2016-05-10 10:58:34 -07001#!/usr/bin/env python
Chetan Gaonkercfcce782016-05-10 10:10:42 -07002#
3# Copyright 2016-present Ciena Corporation
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
Chetan Gaonker25470972016-02-26 08:52:15 -080017from socket import *
18from struct import *
19from scapy.all import *
20from itertools import *
21
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080022IGMP_TYPE_MEMBERSHIP_QUERY = 0x11
23IGMP_TYPE_V3_MEMBERSHIP_REPORT = 0x22
24IGMP_TYPE_V1_MEMBERSHIP_REPORT = 0x12
25IGMP_TYPE_V2_MEMBERSHIP_REPORT = 0x16
26IGMP_TYPE_V2_LEAVE_GROUP = 0x17
27
28IGMP_V3_GR_TYPE_INCLUDE = 0x01
29IGMP_V3_GR_TYPE_EXCLUDE = 0x02
30IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE = 0x03
31IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE = 0x04
32IGMP_V3_GR_TYPE_ALLOW_NEW = 0x05
33IGMP_V3_GR_TYPE_BLOCK_OLD = 0x06
34
35"""
Chetan Gaonker25470972016-02-26 08:52:15 -080036IGMPV3_ALL_ROUTERS = '224.0.0.22'
37IGMPv3 = 3
38IP_SRC = '1.2.3.4'
39ETHERTYPE_IP = 0x0800
40IGMP_DST_MAC = "01:00:5e:00:01:01"
41IGMP_SRC_MAC = "5a:e1:ac:ec:4d:a1"
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080042"""
Chetan Gaonker25470972016-02-26 08:52:15 -080043
Chetan Gaonker25470972016-02-26 08:52:15 -080044
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080045class IGMPv3gr(Packet):
46 """IGMPv3 Group Record, used in membership report"""
Chetan Gaonker25470972016-02-26 08:52:15 -080047
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080048 name = "IGMPv3gr"
Chetan Gaonker25470972016-02-26 08:52:15 -080049
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080050 igmp_v3_gr_types = {
51 IGMP_V3_GR_TYPE_INCLUDE: "Include Mode",
52 IGMP_V3_GR_TYPE_EXCLUDE: "Exclude Mode",
53 IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE: "Change to Include Mode",
54 IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE: "Change to Exclude Mode",
55 IGMP_V3_GR_TYPE_ALLOW_NEW: "Allow New Sources",
56 IGMP_V3_GR_TYPE_BLOCK_OLD: "Block Old Sources"
57 }
Chetan Gaonker25470972016-02-26 08:52:15 -080058
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080059 fields_desc = [
60 ByteEnumField("rtype", IGMP_V3_GR_TYPE_INCLUDE, igmp_v3_gr_types),
61 ByteField("aux_data_len", 0),
62 FieldLenField("numsrc", None, count_of="sources"),
63 IPField("mcaddr", "0.0.0.0"),
64 FieldListField("sources", None, IPField("src", "0.0.0.0"), "numsrc")
65 ]
Chetan Gaonker25470972016-02-26 08:52:15 -080066
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080067 def post_build(self, pkt, payload):
68 pkt += payload
69 if self.aux_data_len != 0:
70 print "WARNING: Auxiliary Data Length must be zero (0)"
Chetan Gaonker25470972016-02-26 08:52:15 -080071 return pkt
72
Chetan Gaonker25470972016-02-26 08:52:15 -080073
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080074class IGMPv3(Packet):
Chetan Gaonker25470972016-02-26 08:52:15 -080075
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080076 name = "IGMPv3"
Chetan Gaonker25470972016-02-26 08:52:15 -080077
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080078 igmp_v3_types = {
79 IGMP_TYPE_MEMBERSHIP_QUERY: "Membership Query",
80 IGMP_TYPE_V3_MEMBERSHIP_REPORT: " Version 3 Mebership Report",
81 IGMP_TYPE_V2_MEMBERSHIP_REPORT: " Version 2 Mebership Report",
82 IGMP_TYPE_V1_MEMBERSHIP_REPORT: " Version 1 Mebership Report",
83 IGMP_TYPE_V2_LEAVE_GROUP: "Version 2 Leave Group"
84 }
Chetan Gaonker25470972016-02-26 08:52:15 -080085
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080086 fields_desc = [
87 ByteEnumField("type", IGMP_TYPE_MEMBERSHIP_QUERY, igmp_v3_types),
88 ByteField("max_resp_code", 0),
89 XShortField("checksum", None),
90 #IPField("group_address", "0.0.0.0"),
Chetan Gaonker25470972016-02-26 08:52:15 -080091
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080092 # membership query fields
93 ConditionalField(IPField("gaddr", "0.0.0.0"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
94 ConditionalField(BitField("resv", 0, 4), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
95 ConditionalField(BitField("s", 0, 1), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
96 ConditionalField(BitField("qrv", 0, 3), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
97 ConditionalField(ByteField("qqic", 0), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
98 ConditionalField(FieldLenField("numsrc", None, count_of="srcs"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
99 ConditionalField(FieldListField("srcs", None, IPField("src", "0.0.0.0"), "numsrc"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
100
101 # membership report fields
102 ConditionalField(ShortField("resv2", 0), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT),
103 ConditionalField(FieldLenField("numgrp", None, count_of="grps"), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT),
104 ConditionalField(PacketListField("grps", [], IGMPv3gr), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT)
105
106 # TODO: v2 and v3 membership reports?
107
108 ]
109
110 def post_build(self, pkt, payload):
111
112 pkt += payload
113
114 if self.type in [IGMP_TYPE_V3_MEMBERSHIP_REPORT,]: # max_resp_code field is reserved (0)
115 mrc = 0
116 else:
117 mrc = self.encode_float(self.max_resp_code)
118 pkt = pkt[:1] + chr(mrc) + pkt[2:]
119
120 if self.checksum is None:
121 chksum = checksum(pkt)
122 pkt = pkt[:2] + chr(chksum >> 8) + chr(chksum & 0xff) + pkt[4:]
123
124 return pkt
125
126 def encode_float(self, value):
127 """Encode max response time value per RFC 3376."""
128 if value < 128:
129 return value
130 if value > 31743:
131 return 255
132 exp = 0
133 value >>= 3
134 while value > 31:
135 exp += 1
136 value >>= 1
137 return 0x80 | (exp << 4) | (value & 0xf)
138
139
140 def decode_float(self, code):
141 if code < 128:
142 return code
143 mant = code & 0xf
144 exp = (code >> 4) & 0x7
145 return (mant | 0x10) << (exp + 3)
146
147 @staticmethod
148 def is_valid_mcaddr(ip):
149 byte1 = atol(ip) >> 24 & 0xff
150 return (byte1 & 0xf0) == 0xe0
151
152 @staticmethod
153 def fixup(pkt):
154 """Fixes up the underlying IP() and Ether() headers."""
155 assert pkt.haslayer(IGMPv3), "This packet is not an IGMPv4 packet; cannot fix it up"
156
157 igmp = pkt.getlayer(IGMPv3)
158
159 if pkt.haslayer(IP):
160 ip = pkt.getlayer(IP)
161 ip.ttl = 1
162 ip.proto = 2
163 ip.tos = 0xc0
164 ip.options = [IPOption_Router_Alert()]
165
166 if igmp.type == IGMP_TYPE_MEMBERSHIP_QUERY:
167 if igmp.gaddr == "0.0.0.0":
168 ip.dst = "224.0.0.1"
169 else:
170 assert IGMPv3.is_valid_mcaddr(igmp.gaddr), "IGMP membership query with invalid mcast address"
171 ip.dst = igmp.gaddr
172
173 elif igmp.type == IGMP_TYPE_V2_LEAVE_GROUP and IGMPv3.is_valid_mcaddr(igmp.gaddr):
174 ip.dst = "224.0.0.2"
175
176 elif (igmp.type in (IGMP_TYPE_V1_MEMBERSHIP_REPORT, IGMP_TYPE_V2_MEMBERSHIP_REPORT) and
177 IGMPv3.is_valid_mcaddr(igmp.gaddr)):
178 ip.dst = igmp.gaddr
179
180 # We do not need to fixup the ether layer, it is done by scapy
181 #
182 # if pkt.haslayer(Ether):
183 # eth = pkt.getlayer(Ether)
184 # ip_long = atol(ip.dst)
185 # ether.dst = '01:00:5e:%02x:%02x:%02x' % ( (ip_long >> 16) & 0x7f, (ip_long >> 8) & 0xff, ip_long & 0xff )
186
187
188 return pkt
189
190
191bind_layers(IP, IGMPv3, frag=0, proto=2, ttl=1, tos=0xc0)
192bind_layers(IGMPv3, IGMPv3gr, frag=0, proto=2)
193bind_layers(IGMPv3gr, IGMPv3gr, frag=0, proto=2)
194
195
196if __name__ == "__main__":
197
198 print "test float encoding"
199 from math import log
200 max_expected_error = 1.0 / (2<<3) # four bit precision
201 p = IGMPv3()
202 for v in range(0, 31745):
203 c = p.encode_float(v)
204 d = p.decode_float(c)
205 rel_err = float(v-d)/v if v!=0 else 0.0
206 assert rel_err <= max_expected_error
207
208 print "construct membership query - general query"
209 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120)
210 hexdump(str(mq))
211
212 print "construct membership query - group-specific query"
213 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120, gaddr="224.0.0.1")
214 hexdump(str(mq))
215
216 print "construct membership query - group-and-source-specific query"
217 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120, gaddr="224.0.0.1")
218 mq.srcs = ['1.2.3.4', '5.6.7.8']
219 hexdump(str(mq))
220
221 print "fixup"
222 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY)
223 mq.srcs = ['1.2.3.4', '5.6.7.8']
224 pkt = Ether() / IP() / mq
225 print "before fixup:"
226 hexdump(str(pkt))
227
228 print "after fixup:"
229 IGMPv3.fixup(pkt)
230 hexdump(str(pkt))
231
232 print "construct v3 membership report - join a single group"
233 mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
234 mr.grps = [IGMPv3gr( rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.30")]
235 hexdump(mr)
236
237 print "construct v3 membership report - join two groups"
238 mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
239 mr.grps = [
240 IGMPv3gr(rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.30"),
241 IGMPv3gr(rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.31")
242 ]
243 hexdump(mr)
244
245 print "construct v3 membership report - leave a group"
246 mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
247 mr.grps = [IGMPv3gr(rtype=IGMP_V3_GR_TYPE_INCLUDE, mcaddr="229.10.20.30")]
248 hexdump(mr)
249
250 print "all ok"