blob: a747d0945a055e87e318b58a7734a32fd87cc350 [file] [log] [blame]
Zsolt Harasztife525502016-03-02 05:18:52 +00001from socket import *
2from struct import *
3from scapy.all import *
4from itertools import *
5
6IGMP_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"""
20IGMPV3_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"
26"""
27
28class IGMPv3(Packet):
29
30 name = "IGMPv3"
31
32 igmp_v3_types = {
33 IGMP_TYPE_MEMBERSHIP_QUERY: "Membership Query",
34 IGMP_TYPE_V3_MEMBERSHIP_REPORT: " Version 3 Mebership Report",
35 IGMP_TYPE_V2_MEMBERSHIP_REPORT: " Version 2 Mebership Report",
36 IGMP_TYPE_V1_MEMBERSHIP_REPORT: " Version 1 Mebership Report",
37 IGMP_TYPE_V2_LEAVE_GROUP: "Version 2 Leave Group"
38 }
39
40 fields_desc = [
41 ByteEnumField("type", IGMP_TYPE_MEMBERSHIP_QUERY, igmp_v3_types),
42 ByteField("max_resp_code", 0),
43 XShortField("checksum", None),
44 #IPField("group_address", "0.0.0.0"),
45
46 # membership query fields
47 ConditionalField(IPField("gaddr", "0.0.0.0"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
48 ConditionalField(BitField("resv", 0, 4), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
49 ConditionalField(BitField("s", 0, 1), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
50 ConditionalField(BitField("qrv", 0, 3), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
51 ConditionalField(ByteField("qqic", 0), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
52 ConditionalField(FieldLenField("numsrc", None, count_of="srcs"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY),
53 ConditionalField(FieldListField("srcs", None, IPField("src", "0.0.0.0"), "numsrc"), lambda pkt: pkt.type == IGMP_TYPE_MEMBERSHIP_QUERY)
54
55 # membership report fields
56 # TODO
57
58 ]
59
60 def post_build(self, pkt, payload):
61
62 pkt += payload
63
64 if self.type in [IGMP_TYPE_V3_MEMBERSHIP_REPORT,]: # max_resp_code field is reserved (0)
65 mrc = 0
66 else:
67 mrc = self.encode_float(self.max_resp_code)
68 pkt = pkt[:1] + chr(mrc) + pkt[2:]
69
70 if self.checksum is None:
71 chksum = checksum(pkt)
72 pkt = pkt[:2] + chr(chksum >> 8) + chr(chksum & 0xff) + pkt[4:]
73
74 return pkt
75
76 def encode_float(self, value):
77 """Encode max response time value per RFC 3376."""
78 if value < 128:
79 return value
80 if value > 31743:
81 return 255
82 exp = 0
83 value >>= 3
84 while value > 31:
85 exp += 1
86 value >>= 1
87 return 0x80 | (exp << 4) | (value & 0xf)
88
89
90 def decode_float(self, code):
91 if code < 128:
92 return code
93 mant = code & 0xf
94 exp = (code >> 4) & 0x7
95 return (mant | 0x10) << (exp + 3)
96
97 @staticmethod
98 def is_valid_mcaddr(ip):
99 byte1 = atol(ip) >> 24 & 0xff
100 return (byte1 & 0xf0) == 0xe0
101
102 @staticmethod
103 def fixup(pkt):
104 """Fixes up the underlying IP() and Ether() headers."""
105 assert pkt.haslayer(IGMPv3), "This packet is not an IGMPv4 packet; cannot fix it up"
106
107 igmp = pkt.getlayer(IGMPv3)
108
109 if pkt.haslayer(IP):
110 ip = pkt.getlayer(IP)
111 ip.ttl = 1
112 ip.proto = 2
113 ip.tos = 0xc0
114 ip.options = [IPOption_Router_Alert()]
115
116 if igmp.type == IGMP_TYPE_MEMBERSHIP_QUERY:
117 if igmp.gaddr == "0.0.0.0":
118 ip.dst = "224.0.0.1"
119 else:
120 assert IGMPv3.is_valid_mcaddr(igmp.gaddr), "IGMP membership query with invalid mcast address"
121 ip.dst = igmp.gaddr
122
123 elif igmp.type == IGMP_TYPE_V2_LEAVE_GROUP and IGMPv3.is_valid_mcaddr(igmp.gaddr):
124 ip.dst = "224.0.0.2"
125
126 elif (igmp.type in (IGMP_TYPE_V1_MEMBERSHIP_REPORT, IGMP_TYPE_V2_MEMBERSHIP_REPORT) and
127 IGMPv3.is_valid_mcaddr(igmp.gaddr)):
128 ip.dst = igmp.gaddr
129
130 # We do not need to fixup the ether layer, it is done by scapy
131 #
132 # if pkt.haslayer(Ether):
133 # eth = pkt.getlayer(Ether)
134 # ip_long = atol(ip.dst)
135 # ether.dst = '01:00:5e:%02x:%02x:%02x' % ( (ip_long >> 16) & 0x7f, (ip_long >> 8) & 0xff, ip_long & 0xff )
136
137
138 return pkt
139
140
141class IGMPv3gr(Packet):
142
143 name = "IGMPv3gr"
144
145 igmp_v3_gr_types = {
146 IGMP_V3_GR_TYPE_INCLUDE: "Include Mode",
147 IGMP_V3_GR_TYPE_EXCLUDE: "Exclude Mode",
148 IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE: "Change to Include Mode",
149 IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE: "Change to Exclude Mode",
150 IGMP_V3_GR_TYPE_ALLOW_NEW: "Allow New Sources",
151 IGMP_V3_GR_TYPE_BLOCK_OLD: "Block Old Sources"
152 }
153
154 fields_desc = [
155 ByteEnumField("rtype", IGMP_V3_GR_TYPE_INCLUDE, igmp_v3_gr_types),
156 ByteField("aux_data_len", 0),
157 FieldLenField("numsrc", None, "sources"),
158 IPField("mcast_addr", "0.0.0.0"),
159 FieldListField("sources", None, IPField("src", "0.0.0.0"), "numsrc")
160 ]
161
162 def post_build(self, pkt, payload):
163 pkt += payload
164 if self.aux_data_len != 0:
165 print "WARNING: Auxiliary Data Length must be zero (0)"
166 return pkt
167
168
169bind_layers(IP, IGMPv3, frag=0, proto=2, ttl=1, tos=0xc0)
170bind_layers(IGMPv3, IGMPv3gr, frag=0, proto=2)
171bind_layers(IGMPv3gr, IGMPv3gr, frag=0, proto=2)
172
173
174if __name__ == "__main__":
175
176 print "test float encoding"
177 from math import log
178 max_expected_error = 1.0 / (2<<3) # four bit precision
179 p = IGMPv3()
180 for v in range(0, 31745):
181 c = p.encode_float(v)
182 d = p.decode_float(c)
183 rel_err = float(v-d)/v if v!=0 else 0.0
184 assert rel_err <= max_expected_error
185
186 print "construct membership query - general query"
187 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120)
188 hexdump(str(mq))
189
190 print "construct membership query - group-specific query"
191 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120, gaddr="224.0.0.1")
192 hexdump(str(mq))
193
194 print "construct membership query - group-and-source-specific query"
195 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120, gaddr="224.0.0.1")
196 mq.srcs = ['1.2.3.4', '5.6.7.8']
197 hexdump(str(mq))
198
199 print "fixup"
200 mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY)
201 mq.srcs = ['1.2.3.4', '5.6.7.8']
202 pkt = Ether() / IP() / mq
203 print "before fixup:"
204 hexdump(str(pkt))
205
206 print "after fixup:"
207 IGMPv3.fixup(pkt)
208 hexdump(str(pkt))
209
210 print "all ok"