blob: 1b24de39780a629937a463bfd714c8049afa277d [file] [log] [blame]
Chetan Gaonker25470972016-02-26 08:52:15 -08001from socket import *
2from struct import *
3from scapy.all import *
4from itertools import *
5
Chetan Gaonker5a5204e2016-03-02 01:35:13 -08006IGMP_TYPE_MEMBERSHIP_QUERY = 0x11
7IGMP_TYPE_V3_MEMBERSHIP_REPORT = 0x22
8IGMP_TYPE_V1_MEMBERSHIP_REPORT = 0x12
9IGMP_TYPE_V2_MEMBERSHIP_REPORT = 0x16
10IGMP_TYPE_V2_LEAVE_GROUP = 0x17
11
12IGMP_V3_GR_TYPE_INCLUDE = 0x01
13IGMP_V3_GR_TYPE_EXCLUDE = 0x02
14IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE = 0x03
15IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE = 0x04
16IGMP_V3_GR_TYPE_ALLOW_NEW = 0x05
17IGMP_V3_GR_TYPE_BLOCK_OLD = 0x06
18
19"""
Chetan Gaonker25470972016-02-26 08:52:15 -080020IGMPV3_ALL_ROUTERS = '224.0.0.22'
21IGMPv3 = 3
22IP_SRC = '1.2.3.4'
23ETHERTYPE_IP = 0x0800
24IGMP_DST_MAC = "01:00:5e:00:01:01"
25IGMP_SRC_MAC = "5a:e1:ac:ec:4d:a1"
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080026"""
Chetan Gaonker25470972016-02-26 08:52:15 -080027
Chetan Gaonker25470972016-02-26 08:52:15 -080028
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080029class IGMPv3gr(Packet):
30 """IGMPv3 Group Record, used in membership report"""
Chetan Gaonker25470972016-02-26 08:52:15 -080031
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080032 name = "IGMPv3gr"
Chetan Gaonker25470972016-02-26 08:52:15 -080033
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080034 igmp_v3_gr_types = {
35 IGMP_V3_GR_TYPE_INCLUDE: "Include Mode",
36 IGMP_V3_GR_TYPE_EXCLUDE: "Exclude Mode",
37 IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE: "Change to Include Mode",
38 IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE: "Change to Exclude Mode",
39 IGMP_V3_GR_TYPE_ALLOW_NEW: "Allow New Sources",
40 IGMP_V3_GR_TYPE_BLOCK_OLD: "Block Old Sources"
41 }
Chetan Gaonker25470972016-02-26 08:52:15 -080042
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080043 fields_desc = [
44 ByteEnumField("rtype", IGMP_V3_GR_TYPE_INCLUDE, igmp_v3_gr_types),
45 ByteField("aux_data_len", 0),
46 FieldLenField("numsrc", None, count_of="sources"),
47 IPField("mcaddr", "0.0.0.0"),
48 FieldListField("sources", None, IPField("src", "0.0.0.0"), "numsrc")
49 ]
Chetan Gaonker25470972016-02-26 08:52:15 -080050
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080051 def post_build(self, pkt, payload):
52 pkt += payload
53 if self.aux_data_len != 0:
54 print "WARNING: Auxiliary Data Length must be zero (0)"
Chetan Gaonker25470972016-02-26 08:52:15 -080055 return pkt
56
Chetan Gaonker25470972016-02-26 08:52:15 -080057
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080058class IGMPv3(Packet):
Chetan Gaonker25470972016-02-26 08:52:15 -080059
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080060 name = "IGMPv3"
Chetan Gaonker25470972016-02-26 08:52:15 -080061
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080062 igmp_v3_types = {
63 IGMP_TYPE_MEMBERSHIP_QUERY: "Membership Query",
64 IGMP_TYPE_V3_MEMBERSHIP_REPORT: " Version 3 Mebership Report",
65 IGMP_TYPE_V2_MEMBERSHIP_REPORT: " Version 2 Mebership Report",
66 IGMP_TYPE_V1_MEMBERSHIP_REPORT: " Version 1 Mebership Report",
67 IGMP_TYPE_V2_LEAVE_GROUP: "Version 2 Leave Group"
68 }
Chetan Gaonker25470972016-02-26 08:52:15 -080069
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080070 fields_desc = [
71 ByteEnumField("type", IGMP_TYPE_MEMBERSHIP_QUERY, igmp_v3_types),
72 ByteField("max_resp_code", 0),
73 XShortField("checksum", None),
74 #IPField("group_address", "0.0.0.0"),
Chetan Gaonker25470972016-02-26 08:52:15 -080075
Chetan Gaonker5a5204e2016-03-02 01:35:13 -080076 # membership query fields
77 ConditionalField(IPField("gaddr", "0.0.0.0"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
78 ConditionalField(BitField("resv", 0, 4), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
79 ConditionalField(BitField("s", 0, 1), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
80 ConditionalField(BitField("qrv", 0, 3), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
81 ConditionalField(ByteField("qqic", 0), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
82 ConditionalField(FieldLenField("numsrc", None, count_of="srcs"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
83 ConditionalField(FieldListField("srcs", None, IPField("src", "0.0.0.0"), "numsrc"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
84
85 # membership report fields
86 ConditionalField(ShortField("resv2", 0), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT),
87 ConditionalField(FieldLenField("numgrp", None, count_of="grps"), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT),
88 ConditionalField(PacketListField("grps", [], IGMPv3gr), lambda pkt: pkt.type == IGMP_TYPE_V3_MEMBERSHIP_REPORT)
89
90 # TODO: v2 and v3 membership reports?
91
92 ]
93
94 def post_build(self, pkt, payload):
95
96 pkt += payload
97
98 if self.type in [IGMP_TYPE_V3_MEMBERSHIP_REPORT,]: # max_resp_code field is reserved (0)
99 mrc = 0
100 else:
101 mrc = self.encode_float(self.max_resp_code)
102 pkt = pkt[:1] + chr(mrc) + pkt[2:]
103
104 if self.checksum is None:
105 chksum = checksum(pkt)
106 pkt = pkt[:2] + chr(chksum >> 8) + chr(chksum & 0xff) + pkt[4:]
107
108 return pkt
109
110 def encode_float(self, value):
111 """Encode max response time value per RFC 3376."""
112 if value < 128:
113 return value
114 if value > 31743:
115 return 255
116 exp = 0
117 value >>= 3
118 while value > 31:
119 exp += 1
120 value >>= 1
121 return 0x80 | (exp << 4) | (value & 0xf)
122
123
124 def decode_float(self, code):
125 if code < 128:
126 return code
127 mant = code & 0xf
128 exp = (code >> 4) & 0x7
129 return (mant | 0x10) << (exp + 3)
130
131 @staticmethod
132 def is_valid_mcaddr(ip):
133 byte1 = atol(ip) >> 24 & 0xff
134 return (byte1 & 0xf0) == 0xe0
135
136 @staticmethod
137 def fixup(pkt):
138 """Fixes up the underlying IP() and Ether() headers."""
139 assert pkt.haslayer(IGMPv3), "This packet is not an IGMPv4 packet; cannot fix it up"
140
141 igmp = pkt.getlayer(IGMPv3)
142
143 if pkt.haslayer(IP):
144 ip = pkt.getlayer(IP)
145 ip.ttl = 1
146 ip.proto = 2
147 ip.tos = 0xc0
148 ip.options = [IPOption_Router_Alert()]
149
150 if igmp.type == IGMP_TYPE_MEMBERSHIP_QUERY:
151 if igmp.gaddr == "0.0.0.0":
152 ip.dst = "224.0.0.1"
153 else:
154 assert IGMPv3.is_valid_mcaddr(igmp.gaddr), "IGMP membership query with invalid mcast address"
155 ip.dst = igmp.gaddr
156
157 elif igmp.type == IGMP_TYPE_V2_LEAVE_GROUP and IGMPv3.is_valid_mcaddr(igmp.gaddr):
158 ip.dst = "224.0.0.2"
159
160 elif (igmp.type in (IGMP_TYPE_V1_MEMBERSHIP_REPORT, IGMP_TYPE_V2_MEMBERSHIP_REPORT) and
161 IGMPv3.is_valid_mcaddr(igmp.gaddr)):
162 ip.dst = igmp.gaddr
163
164 # We do not need to fixup the ether layer, it is done by scapy
165 #
166 # if pkt.haslayer(Ether):
167 # eth = pkt.getlayer(Ether)
168 # ip_long = atol(ip.dst)
169 # ether.dst = '01:00:5e:%02x:%02x:%02x' % ( (ip_long >> 16) & 0x7f, (ip_long >> 8) & 0xff, ip_long & 0xff )
170
171
172 return pkt
173
174
175bind_layers(IP, IGMPv3, frag=0, proto=2, ttl=1, tos=0xc0)
176bind_layers(IGMPv3, IGMPv3gr, frag=0, proto=2)
177bind_layers(IGMPv3gr, IGMPv3gr, frag=0, proto=2)
178
179
180if __name__ == "__main__":
181
182 print "test float encoding"
183 from math import log
184 max_expected_error = 1.0 / (2<<3) # four bit precision
185 p = IGMPv3()
186 for v in range(0, 31745):
187 c = p.encode_float(v)
188 d = p.decode_float(c)
189 rel_err = float(v-d)/v if v!=0 else 0.0
190 assert rel_err <= max_expected_error
191
192 print "construct membership query - general query"
193 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120)
194 hexdump(str(mq))
195
196 print "construct membership query - group-specific query"
197 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120, gaddr="224.0.0.1")
198 hexdump(str(mq))
199
200 print "construct membership query - group-and-source-specific query"
201 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120, gaddr="224.0.0.1")
202 mq.srcs = ['1.2.3.4', '5.6.7.8']
203 hexdump(str(mq))
204
205 print "fixup"
206 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY)
207 mq.srcs = ['1.2.3.4', '5.6.7.8']
208 pkt = Ether() / IP() / mq
209 print "before fixup:"
210 hexdump(str(pkt))
211
212 print "after fixup:"
213 IGMPv3.fixup(pkt)
214 hexdump(str(pkt))
215
216 print "construct v3 membership report - join a single group"
217 mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
218 mr.grps = [IGMPv3gr( rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.30")]
219 hexdump(mr)
220
221 print "construct v3 membership report - join two groups"
222 mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
223 mr.grps = [
224 IGMPv3gr(rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.30"),
225 IGMPv3gr(rtype=IGMP_V3_GR_TYPE_EXCLUDE, mcaddr="229.10.20.31")
226 ]
227 hexdump(mr)
228
229 print "construct v3 membership report - leave a group"
230 mr = IGMPv3(type=IGMP_TYPE_V3_MEMBERSHIP_REPORT, max_resp_code=30, gaddr="224.0.0.1")
231 mr.grps = [IGMPv3gr(rtype=IGMP_V3_GR_TYPE_INCLUDE, mcaddr="229.10.20.30")]
232 hexdump(mr)
233
234 print "all ok"