Matteo Scandolo | 48d3d2d | 2017-08-08 13:05:27 -0700 | [diff] [blame] | 1 | |
| 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 Gaonker | cfcce78 | 2016-05-10 10:10:42 -0700 | [diff] [blame] | 17 | # 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 Gaonker | cfcce78 | 2016-05-10 10:10:42 -0700 | [diff] [blame] | 22 | # http://www.apache.org/licenses/LICENSE-2.0 |
Chetan Gaonker | cfcce78 | 2016-05-10 10:10:42 -0700 | [diff] [blame] | 23 | # 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 Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 29 | from socket import * |
| 30 | from struct import * |
| 31 | from scapy.all import * |
| 32 | from itertools import * |
| 33 | |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 34 | IGMP_TYPE_MEMBERSHIP_QUERY = 0x11 |
| 35 | IGMP_TYPE_V3_MEMBERSHIP_REPORT = 0x22 |
Thangavelu K S | ef6f0a5 | 2016-12-14 19:57:05 +0000 | [diff] [blame] | 36 | IGMP_TYPE_V3_MEMBERSHIP_REPORT_NEGATIVE = 0xdd |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 37 | IGMP_TYPE_V1_MEMBERSHIP_REPORT = 0x12 |
| 38 | IGMP_TYPE_V2_MEMBERSHIP_REPORT = 0x16 |
| 39 | IGMP_TYPE_V2_LEAVE_GROUP = 0x17 |
| 40 | |
| 41 | IGMP_V3_GR_TYPE_INCLUDE = 0x01 |
Thangavelu K S | ef6f0a5 | 2016-12-14 19:57:05 +0000 | [diff] [blame] | 42 | IGMP_V3_GR_TYPE_INCLUDE_NEGATIVE = 0xaa |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 43 | IGMP_V3_GR_TYPE_EXCLUDE = 0x02 |
| 44 | IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE = 0x03 |
| 45 | IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE = 0x04 |
| 46 | IGMP_V3_GR_TYPE_ALLOW_NEW = 0x05 |
| 47 | IGMP_V3_GR_TYPE_BLOCK_OLD = 0x06 |
| 48 | |
| 49 | """ |
Chetan Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 50 | IGMPV3_ALL_ROUTERS = '224.0.0.22' |
| 51 | IGMPv3 = 3 |
| 52 | IP_SRC = '1.2.3.4' |
| 53 | ETHERTYPE_IP = 0x0800 |
| 54 | IGMP_DST_MAC = "01:00:5e:00:01:01" |
| 55 | IGMP_SRC_MAC = "5a:e1:ac:ec:4d:a1" |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 56 | """ |
Chetan Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 57 | |
Chetan Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 58 | |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 59 | class IGMPv3gr(Packet): |
| 60 | """IGMPv3 Group Record, used in membership report""" |
Chetan Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 61 | |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 62 | name = "IGMPv3gr" |
Chetan Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 63 | |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 64 | igmp_v3_gr_types = { |
| 65 | IGMP_V3_GR_TYPE_INCLUDE: "Include Mode", |
Thangavelu K S | ef6f0a5 | 2016-12-14 19:57:05 +0000 | [diff] [blame] | 66 | IGMP_V3_GR_TYPE_INCLUDE_NEGATIVE: "Include Mode in negative scenario", |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 67 | 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 Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 73 | |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 74 | 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 Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 81 | |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 82 | def post_build(self, pkt, payload): |
| 83 | pkt += payload |
| 84 | if self.aux_data_len != 0: |
A R Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 85 | print("WARNING: Auxiliary Data Length must be zero (0)") |
Chetan Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 86 | return pkt |
| 87 | |
Chetan Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 88 | |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 89 | class IGMPv3(Packet): |
Chetan Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 90 | |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 91 | name = "IGMPv3" |
Chetan Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 92 | |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 93 | 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 Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 100 | |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 101 | 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 Gaonker | 2547097 | 2016-02-26 08:52:15 -0800 | [diff] [blame] | 106 | |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 107 | # 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 S | ef6f0a5 | 2016-12-14 19:57:05 +0000 | [diff] [blame] | 168 | def fixup(pkt, invalid_ttl = None): |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 169 | """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 S | ef6f0a5 | 2016-12-14 19:57:05 +0000 | [diff] [blame] | 176 | if invalid_ttl is None: |
| 177 | ip.ttl = 1 |
| 178 | else: |
| 179 | ip.ttl = 20 |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 180 | 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 | |
| 209 | bind_layers(IP, IGMPv3, frag=0, proto=2, ttl=1, tos=0xc0) |
| 210 | bind_layers(IGMPv3, IGMPv3gr, frag=0, proto=2) |
| 211 | bind_layers(IGMPv3gr, IGMPv3gr, frag=0, proto=2) |
| 212 | |
| 213 | |
| 214 | if __name__ == "__main__": |
| 215 | |
A R Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 216 | print("test float encoding") |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 217 | 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 Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 226 | print("construct membership query - general query") |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 227 | mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120) |
| 228 | hexdump(str(mq)) |
| 229 | |
A R Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 230 | print("construct membership query - group-specific query") |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 231 | mq = IGMPv3(type=IGMP_TYPE_MEMBERSHIP_QUERY, max_resp_code=120, gaddr="224.0.0.1") |
| 232 | hexdump(str(mq)) |
| 233 | |
A R Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 234 | print("construct membership query - group-and-source-specific query") |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 235 | 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 Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 239 | print("fixup") |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 240 | 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 Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 243 | print("before fixup:") |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 244 | hexdump(str(pkt)) |
| 245 | |
A R Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 246 | print("after fixup:") |
Thangavelu K S | ef6f0a5 | 2016-12-14 19:57:05 +0000 | [diff] [blame] | 247 | |
| 248 | IGMPv3.fixup(pkt,'no') |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 249 | hexdump(str(pkt)) |
| 250 | |
A R Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 251 | print("construct v3 membership report - join a single group") |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 252 | 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 Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 256 | print("construct v3 membership report - join two groups") |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 257 | 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 Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 264 | print("construct v3 membership report - leave a group") |
Chetan Gaonker | 5a5204e | 2016-03-02 01:35:13 -0800 | [diff] [blame] | 265 | 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 Karthick | 76a497a | 2017-04-12 10:59:39 -0700 | [diff] [blame] | 269 | print("all ok") |