alshabib | 9c19c52 | 2017-01-11 15:24:48 -0600 | [diff] [blame] | 1 | # 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 | # |
| 13 | from socket import * |
| 14 | from struct import * |
| 15 | from scapy.all import * |
| 16 | from itertools import * |
| 17 | |
| 18 | IGMP_TYPE_MEMBERSHIP_QUERY = 0x11 |
| 19 | IGMP_TYPE_V3_MEMBERSHIP_REPORT = 0x22 |
| 20 | IGMP_TYPE_V3_MEMBERSHIP_REPORT_NEGATIVE = 0xdd |
| 21 | IGMP_TYPE_V1_MEMBERSHIP_REPORT = 0x12 |
| 22 | IGMP_TYPE_V2_MEMBERSHIP_REPORT = 0x16 |
| 23 | IGMP_TYPE_V2_LEAVE_GROUP = 0x17 |
| 24 | |
| 25 | IGMP_V3_GR_TYPE_INCLUDE = 0x01 |
| 26 | IGMP_V3_GR_TYPE_INCLUDE_NEGATIVE = 0xaa |
| 27 | IGMP_V3_GR_TYPE_EXCLUDE = 0x02 |
| 28 | IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE = 0x03 |
| 29 | IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE = 0x04 |
| 30 | IGMP_V3_GR_TYPE_ALLOW_NEW = 0x05 |
| 31 | IGMP_V3_GR_TYPE_BLOCK_OLD = 0x06 |
| 32 | |
| 33 | """ |
| 34 | IGMPV3_ALL_ROUTERS = '224.0.0.22' |
| 35 | IGMPv3 = 3 |
| 36 | IP_SRC = '1.2.3.4' |
| 37 | ETHERTYPE_IP = 0x0800 |
| 38 | IGMP_DST_MAC = "01:00:5e:00:01:01" |
| 39 | IGMP_SRC_MAC = "5a:e1:ac:ec:4d:a1" |
| 40 | """ |
| 41 | |
| 42 | |
| 43 | class 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 | |
| 73 | class 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 | |
| 193 | bind_layers(IP, IGMPv3, frag=0, proto=2, ttl=1, tos=0xc0) |
| 194 | bind_layers(IGMPv3, IGMPv3gr, frag=0, proto=2) |
| 195 | bind_layers(IGMPv3gr, IGMPv3gr, frag=0, proto=2) |
| 196 | |
| 197 | |
| 198 | if __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" |