1 #! /usr/bin/env python 2 3 # This file is part of Scapy 4 # Scapy is free software: you can redistribute it and/or modify 5 # it under the terms of the GNU General Public License as published by 6 # the Free Software Foundation, either version 2 of the License, or 7 # any later version. 8 # 9 # Scapy is distributed in the hope that it will be useful, 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 # GNU General Public License for more details. 13 # 14 # You should have received a copy of the GNU General Public License 15 # along with Scapy. If not, see <http://www.gnu.org/licenses/>. 16 17 # scapy.contrib.description = IGMP/IGMPv2 18 # scapy.contrib.status = loads 19 20 from __future__ import print_function 21 from scapy.packet import * 22 from scapy.fields import * 23 from scapy.layers.inet import * 24 from scapy.layers.l2 import DestMACField, getmacbyip 25 from scapy.error import warning 26 from scapy.compat import chb, orb 27 28 def isValidMCAddr(ip): 29 """convert dotted quad string to long and check the first octet""" 30 FirstOct=atol(ip)>>24 & 0xFF 31 return (FirstOct >= 224) and (FirstOct <= 239) 32 33 class IGMP(Packet): 34 """IGMP Message Class for v1 and v2. 35 36 This class is derived from class Packet. You need call "igmpize()" 37 so the packet is transformed according the RFC when sent. 38 a=Ether(src="00:01:02:03:04:05") 39 b=IP(src="1.2.3.4") 40 c=IGMP(type=0x12, gaddr="224.2.3.4") 41 x = a/b/c 42 x[IGMP].igmpize() 43 sendp(a/b/c, iface="en0") 44 45 Parameters: 46 type IGMP type field, 0x11, 0x12, 0x16 or 0x17 47 mrcode Maximum Response time (zero for v1) 48 gaddr Multicast Group Address 224.x.x.x/4 49 50 See RFC2236, Section 2. Introduction for definitions of proper 51 IGMPv2 message format http://www.faqs.org/rfcs/rfc2236.html 52 53 """ 54 name = "IGMP" 55 56 igmptypes = { 0x11 : "Group Membership Query", 57 0x12 : "Version 1 - Membership Report", 58 0x16 : "Version 2 - Membership Report", 59 0x17 : "Leave Group"} 60 61 fields_desc = [ ByteEnumField("type", 0x11, igmptypes), 62 ByteField("mrcode", 20), 63 XShortField("chksum", None), 64 IPField("gaddr", "0.0.0.0")] 65 66 def post_build(self, p, pay): 67 """Called implicitly before a packet is sent to compute and place IGMP checksum. 68 69 Parameters: 70 self The instantiation of an IGMP class 71 p The IGMP message in hex in network byte order 72 pay Additional payload for the IGMP message 73 """ 74 p += pay 75 if self.chksum is None: 76 ck = checksum(p) 77 p = p[:2]+chb(ck>>8)+chb(ck&0xff)+p[4:] 78 return p 79 80 @classmethod 81 def dispatch_hook(cls, _pkt=None, *args, **kargs): 82 if _pkt and len(_pkt) >= 4: 83 from scapy.contrib.igmpv3 import IGMPv3 84 if orb(_pkt[0]) in [0x22, 0x30, 0x31, 0x32]: 85 return IGMPv3 86 if orb(_pkt[0]) == 0x11 and len(_pkt) >= 12: 87 return IGMPv3 88 return IGMP 89 90 def igmpize(self): 91 """Called to explicitly fixup the packet according to the IGMP RFC 92 93 The rules are: 94 General: 95 1. the Max Response time is meaningful only in Membership Queries and should be zero 96 IP: 97 1. Send General Group Query to 224.0.0.1 (all systems) 98 2. Send Leave Group to 224.0.0.2 (all routers) 99 3a.Otherwise send the packet to the group address 100 3b.Send reports/joins to the group address 101 4. ttl = 1 (RFC 2236, section 2) 102 5. send the packet with the router alert IP option (RFC 2236, section 2) 103 Ether: 104 1. Recalculate destination 105 106 Returns: 107 True The tuple ether/ip/self passed all check and represents 108 a proper IGMP packet. 109 False One of more validation checks failed and no fields 110 were adjusted. 111 112 The function will examine the IGMP message to assure proper format. 113 Corrections will be attempted if possible. The IP header is then properly 114 adjusted to ensure correct formatting and assignment. The Ethernet header 115 is then adjusted to the proper IGMP packet format. 116 """ 117 gaddr = self.gaddr if hasattr(self, "gaddr") and self.gaddr else "0.0.0.0" 118 underlayer = self.underlayer 119 if not self.type in [0x11, 0x30]: # General Rule 1 120 self.mrcode = 0 121 if isinstance(underlayer, IP): 122 if (self.type == 0x11): 123 if (gaddr == "0.0.0.0"): 124 underlayer.dst = "224.0.0.1" # IP rule 1 125 elif isValidMCAddr(gaddr): 126 underlayer.dst = gaddr # IP rule 3a 127 else: 128 warning("Invalid IGMP Group Address detected !") 129 return False 130 elif ((self.type == 0x17) and isValidMCAddr(gaddr)): 131 underlayer.dst = "224.0.0.2" # IP rule 2 132 elif ((self.type == 0x12) or (self.type == 0x16)) and (isValidMCAddr(gaddr)): 133 underlayer.dst = gaddr # IP rule 3b 134 else: 135 warning("Invalid IGMP Type detected !") 136 return False 137 if not any(isinstance(x, IPOption_Router_Alert) for x in underlayer.options): 138 underlayer.options.append(IPOption_Router_Alert()) 139 _root = self.firstlayer() 140 if _root.haslayer(Ether): 141 # Force recalculate Ether dst 142 _root[Ether].dst = getmacbyip(underlayer.dst) # Ether rule 1 143 from scapy.contrib.igmpv3 import IGMPv3 144 if isinstance(self, IGMPv3): 145 self.encode_maxrespcode() 146 return True 147 148 def mysummary(self): 149 """Display a summary of the IGMP object.""" 150 if isinstance(self.underlayer, IP): 151 return self.underlayer.sprintf("IGMP: %IP.src% > %IP.dst% %IGMP.type% %IGMP.gaddr%") 152 else: 153 return self.sprintf("IGMP %IGMP.type% %IGMP.gaddr%") 154 155 bind_layers( IP, IGMP, frag=0, 156 proto=2, 157 ttl=1) 158