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