Home | History | Annotate | Download | only in contrib
      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