Zsolt Haraszti | fe52550 | 2016-03-02 05:18:52 +0000 | [diff] [blame^] | 1 | from socket import * |
| 2 | from struct import * |
| 3 | from scapy.all import * |
| 4 | from itertools import * |
| 5 | |
| 6 | IGMP_TYPE_MEMBERSHIP_QUERY = 0x11 |
| 7 | IGMP_TYPE_V3_MEMBERSHIP_REPORT = 0x22 |
| 8 | IGMP_TYPE_V1_MEMBERSHIP_REPORT = 0x12 |
| 9 | IGMP_TYPE_V2_MEMBERSHIP_REPORT = 0x16 |
| 10 | IGMP_TYPE_V2_LEAVE_GROUP = 0x17 |
| 11 | |
| 12 | IGMP_V3_GR_TYPE_INCLUDE = 0x01 |
| 13 | IGMP_V3_GR_TYPE_EXCLUDE = 0x02 |
| 14 | IGMP_V3_GR_TYPE_CHANGE_TO_INCLUDE = 0x03 |
| 15 | IGMP_V3_GR_TYPE_CHANGE_TO_EXCLUDE = 0x04 |
| 16 | IGMP_V3_GR_TYPE_ALLOW_NEW = 0x05 |
| 17 | IGMP_V3_GR_TYPE_BLOCK_OLD = 0x06 |
| 18 | |
| 19 | """ |
| 20 | IGMPV3_ALL_ROUTERS = '224.0.0.22' |
| 21 | IGMPv3 = 3 |
| 22 | IP_SRC = '1.2.3.4' |
| 23 | ETHERTYPE_IP = 0x0800 |
| 24 | IGMP_DST_MAC = "01:00:5e:00:01:01" |
| 25 | IGMP_SRC_MAC = "5a:e1:ac:ec:4d:a1" |
| 26 | """ |
| 27 | |
| 28 | class 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 | |
| 141 | class 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 | |
| 169 | bind_layers(IP, IGMPv3, frag=0, proto=2, ttl=1, tos=0xc0) |
| 170 | bind_layers(IGMPv3, IGMPv3gr, frag=0, proto=2) |
| 171 | bind_layers(IGMPv3gr, IGMPv3gr, frag=0, proto=2) |
| 172 | |
| 173 | |
| 174 | if __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" |