blob: 872ab38187064a39a4bacc96404cfa3b9f2073de [file] [log] [blame]
Matteo Scandolo48d3d2d2017-08-08 13:05:27 -07001
2# Copyright 2017-present Open Networking Foundation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16
Chetan Gaonkercfcce782016-05-10 10:10:42 -070017# Copyright 2016-present Ciena Corporation
18#
19# Licensed under the Apache License, Version 2.0 (the "License");
20# you may not use this file except in compliance with the License.
21# You may obtain a copy of the License at
Chetan Gaonkercfcce782016-05-10 10:10:42 -070022# http://www.apache.org/licenses/LICENSE-2.0
Chetan Gaonkercfcce782016-05-10 10:10:42 -070023# Unless required by applicable law or agreed to in writing, software
24# distributed under the License is distributed on an "AS IS" BASIS,
25# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
26# See the License for the specific language governing permissions and
27# limitations under the License.
28#
Chetan Gaonker25470972016-02-26 08:52:15 -080029from socket import *
30from struct import *
31from scapy.all import *
32from itertools import *
33
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080034IGMP_TYPE_MEMBERSHIP_QUERY = 0x11
35IGMP_TYPE_V3_MEMBERSHIP_REPORT = 0x22
Thangavelu K Sef6f0a52016-12-14 19:57:05 +000036IGMP_TYPE_V3_MEMBERSHIP_REPORT_NEGATIVE = 0xdd
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080037IGMP_TYPE_V1_MEMBERSHIP_REPORT = 0x12
38IGMP_TYPE_V2_MEMBERSHIP_REPORT = 0x16
39IGMP_TYPE_V2_LEAVE_GROUP = 0x17
40
41IGMP_V3_GR_TYPE_INCLUDE = 0x01
Thangavelu K Sef6f0a52016-12-14 19:57:05 +000042IGMP_V3_GR_TYPE_INCLUDE_NEGATIVE = 0xaa
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080043IGMP_V3_GR_TYPE_EXCLUDE = 0x02
44IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE = 0x03
45IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE = 0x04
46IGMP_V3_GR_TYPE_ALLOW_NEW = 0x05
47IGMP_V3_GR_TYPE_BLOCK_OLD = 0x06
48
49"""
Chetan Gaonker25470972016-02-26 08:52:15 -080050IGMPV3_ALL_ROUTERS = '224.0.0.22'
51IGMPv3 = 3
52IP_SRC = '1.2.3.4'
53ETHERTYPE_IP = 0x0800
54IGMP_DST_MAC = "01:00:5e:00:01:01"
55IGMP_SRC_MAC = "5a:e1:ac:ec:4d:a1"
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080056"""
Chetan Gaonker25470972016-02-26 08:52:15 -080057
Chetan Gaonker25470972016-02-26 08:52:15 -080058
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080059class IGMPv3gr(Packet):
60 """IGMPv3 Group Record, used in membership report"""
Chetan Gaonker25470972016-02-26 08:52:15 -080061
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080062 name = "IGMPv3gr"
Chetan Gaonker25470972016-02-26 08:52:15 -080063
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080064 igmp_v3_gr_types = {
65 IGMP_V3_GR_TYPE_INCLUDE: "Include Mode",
Thangavelu K Sef6f0a52016-12-14 19:57:05 +000066 IGMP_V3_GR_TYPE_INCLUDE_NEGATIVE: "Include Mode in negative scenario",
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080067 IGMP_V3_GR_TYPE_EXCLUDE: "Exclude Mode",
68 IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE: "Change to Include Mode",
69 IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE: "Change to Exclude Mode",
70 IGMP_V3_GR_TYPE_ALLOW_NEW: "Allow New Sources",
71 IGMP_V3_GR_TYPE_BLOCK_OLD: "Block Old Sources"
72 }
Chetan Gaonker25470972016-02-26 08:52:15 -080073
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080074 fields_desc = [
75 ByteEnumField("rtype", IGMP_V3_GR_TYPE_INCLUDE, igmp_v3_gr_types),
76 ByteField("aux_data_len", 0),
77 FieldLenField("numsrc", None, count_of="sources"),
78 IPField("mcaddr", "0.0.0.0"),
79 FieldListField("sources", None, IPField("src", "0.0.0.0"), "numsrc")
80 ]
Chetan Gaonker25470972016-02-26 08:52:15 -080081
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080082 def post_build(self, pkt, payload):
83 pkt += payload
84 if self.aux_data_len != 0:
A R Karthick76a497a2017-04-12 10:59:39 -070085 print("WARNING: Auxiliary Data Length must be zero (0)")
Chetan Gaonker25470972016-02-26 08:52:15 -080086 return pkt
87
Chetan Gaonker25470972016-02-26 08:52:15 -080088
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080089class IGMPv3(Packet):
Chetan Gaonker25470972016-02-26 08:52:15 -080090
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080091 name = "IGMPv3"
Chetan Gaonker25470972016-02-26 08:52:15 -080092
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080093 igmp_v3_types = {
94 IGMP_TYPE_MEMBERSHIP_QUERY: "Membership Query",
95 IGMP_TYPE_V3_MEMBERSHIP_REPORT: " Version 3 Mebership Report",
96 IGMP_TYPE_V2_MEMBERSHIP_REPORT: " Version 2 Mebership Report",
97 IGMP_TYPE_V1_MEMBERSHIP_REPORT: " Version 1 Mebership Report",
98 IGMP_TYPE_V2_LEAVE_GROUP: "Version 2 Leave Group"
99 }
Chetan Gaonker25470972016-02-26 08:52:15 -0800100
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800101 fields_desc = [
102 ByteEnumField("type", IGMP_TYPE_MEMBERSHIP_QUERY, igmp_v3_types),
103 ByteField("max_resp_code", 0),
104 XShortField("checksum", None),
105 #IPField("group_address", "0.0.0.0"),
Chetan Gaonker25470972016-02-26 08:52:15 -0800106
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800107 # membership query fields
108 ConditionalField(IPField("gaddr", "0.0.0.0"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
109 ConditionalField(BitField("resv", 0, 4), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
110 ConditionalField(BitField("s", 0, 1), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
111 ConditionalField(BitField("qrv", 0, 3), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
112 ConditionalField(ByteField("qqic", 0), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
113 ConditionalField(FieldLenField("numsrc", None, count_of="srcs"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
114 ConditionalField(FieldListField("srcs", None, IPField("src", "0.0.0.0"), "numsrc"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
115
116 # membership report fields
117 ConditionalField(ShortField("resv2", 0), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT),
118 ConditionalField(FieldLenField("numgrp", None, count_of="grps"), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT),
119 ConditionalField(PacketListField("grps", [], IGMPv3gr), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT)
120
121 # TODO: v2 and v3 membership reports?
122
123 ]
124
125 def post_build(self, pkt, payload):
126
127 pkt += payload
128
129 if self.type in [IGMP_TYPE_V3_MEMBERSHIP_REPORT,]: # max_resp_code field is reserved (0)
130 mrc = 0
131 else:
132 mrc = self.encode_float(self.max_resp_code)
133 pkt = pkt[:1] + chr(mrc) + pkt[2:]
134
135 if self.checksum is None:
136 chksum = checksum(pkt)
137 pkt = pkt[:2] + chr(chksum >> 8) + chr(chksum & 0xff) + pkt[4:]
138
139 return pkt
140
141 def encode_float(self, value):
142 """Encode max response time value per RFC 3376."""
143 if value < 128:
144 return value
145 if value > 31743:
146 return 255
147 exp = 0
148 value >>= 3
149 while value > 31:
150 exp += 1
151 value >>= 1
152 return 0x80 | (exp << 4) | (value & 0xf)
153
154
155 def decode_float(self, code):
156 if code < 128:
157 return code
158 mant = code & 0xf
159 exp = (code >> 4) & 0x7
160 return (mant | 0x10) << (exp + 3)
161
162 @staticmethod
163 def is_valid_mcaddr(ip):
164 byte1 = atol(ip) >> 24 & 0xff
165 return (byte1 & 0xf0) == 0xe0
166
167 @staticmethod
Thangavelu K Sef6f0a52016-12-14 19:57:05 +0000168 def fixup(pkt, invalid_ttl = None):
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800169 """Fixes up the underlying IP() and Ether() headers."""
170 assert pkt.haslayer(IGMPv3), "This packet is not an IGMPv4 packet; cannot fix it up"
171
172 igmp = pkt.getlayer(IGMPv3)
173
174 if pkt.haslayer(IP):
175 ip = pkt.getlayer(IP)
Thangavelu K Sef6f0a52016-12-14 19:57:05 +0000176 if invalid_ttl is None:
177 ip.ttl = 1
178 else:
179 ip.ttl = 20
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800180 ip.proto = 2
181 ip.tos = 0xc0
182 ip.options = [IPOption_Router_Alert()]
183
184 if igmp.type == IGMP_TYPE_MEMBERSHIP_QUERY:
185 if igmp.gaddr == "0.0.0.0":
186 ip.dst = "224.0.0.1"
187 else:
188 assert IGMPv3.is_valid_mcaddr(igmp.gaddr), "IGMP membership query with invalid mcast address"
189 ip.dst = igmp.gaddr
190
191 elif igmp.type == IGMP_TYPE_V2_LEAVE_GROUP and IGMPv3.is_valid_mcaddr(igmp.gaddr):
192 ip.dst = "224.0.0.2"
193
194 elif (igmp.type in (IGMP_TYPE_V1_MEMBERSHIP_REPORT, IGMP_TYPE_V2_MEMBERSHIP_REPORT) and
195 IGMPv3.is_valid_mcaddr(igmp.gaddr)):
196 ip.dst = igmp.gaddr
197
198 # We do not need to fixup the ether layer, it is done by scapy
199 #
200 # if pkt.haslayer(Ether):
201 # eth = pkt.getlayer(Ether)
202 # ip_long = atol(ip.dst)
203 # ether.dst = '01:00:5e:%02x:%02x:%02x' % ( (ip_long >> 16) & 0x7f, (ip_long >> 8) & 0xff, ip_long & 0xff )
204
205
206 return pkt
207
208
209bind_layers(IP, IGMPv3, frag=0, proto=2, ttl=1, tos=0xc0)
210bind_layers(IGMPv3, IGMPv3gr, frag=0, proto=2)
211bind_layers(IGMPv3gr, IGMPv3gr, frag=0, proto=2)
212
213
214if __name__ == "__main__":
215
A R Karthick76a497a2017-04-12 10:59:39 -0700216 print("test float encoding")
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800217 from math import log
218 max_expected_error = 1.0 / (2<<3) # four bit precision
219 p = IGMPv3()
220 for v in range(0, 31745):
221 c = p.encode_float(v)
222 d = p.decode_float(c)
223 rel_err = float(v-d)/v if v!=0 else 0.0
224 assert rel_err <= max_expected_error
225
A R Karthick76a497a2017-04-12 10:59:39 -0700226 print("construct membership query - general query")
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800227 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120)
228 hexdump(str(mq))
229
A R Karthick76a497a2017-04-12 10:59:39 -0700230 print("construct membership query - group-specific query")
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800231 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120, gaddr="224.0.0.1")
232 hexdump(str(mq))
233
A R Karthick76a497a2017-04-12 10:59:39 -0700234 print("construct membership query - group-and-source-specific query")
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800235 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120, gaddr="224.0.0.1")
236 mq.srcs = ['1.2.3.4', '5.6.7.8']
237 hexdump(str(mq))
238
A R Karthick76a497a2017-04-12 10:59:39 -0700239 print("fixup")
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800240 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY)
241 mq.srcs = ['1.2.3.4', '5.6.7.8']
242 pkt = Ether() / IP() / mq
A R Karthick76a497a2017-04-12 10:59:39 -0700243 print("before fixup:")
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800244 hexdump(str(pkt))
245
A R Karthick76a497a2017-04-12 10:59:39 -0700246 print("after fixup:")
Thangavelu K Sef6f0a52016-12-14 19:57:05 +0000247
248 IGMPv3.fixup(pkt,'no')
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800249 hexdump(str(pkt))
250
A R Karthick76a497a2017-04-12 10:59:39 -0700251 print("construct v3 membership report - join a single group")
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800252 mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
253 mr.grps = [IGMPv3gr( rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.30")]
254 hexdump(mr)
255
A R Karthick76a497a2017-04-12 10:59:39 -0700256 print("construct v3 membership report - join two groups")
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800257 mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
258 mr.grps = [
259 IGMPv3gr(rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.30"),
260 IGMPv3gr(rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.31")
261 ]
262 hexdump(mr)
263
A R Karthick76a497a2017-04-12 10:59:39 -0700264 print("construct v3 membership report - leave a group")
Chetan Gaonker5a5204e2016-03-02 01:35:13 -0800265 mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
266 mr.grps = [IGMPv3gr(rtype=IGMP_V3_GR_TYPE_INCLUDE, mcaddr="229.10.20.30")]
267 hexdump(mr)
268
A R Karthick76a497a2017-04-12 10:59:39 -0700269 print("all ok")