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