summaryrefslogtreecommitdiffstats
path: root/scripts/external_libs/scapy-python3-0.18/scapy/contrib/igmpv3.py
blob: 8322c7a380adec1e40619652b5475125153704c9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
#! /usr/bin/env python

# http://trac.secdev.org/scapy/ticket/31

# scapy.contrib.description = IGMPv3
# scapy.contrib.status = loads

from scapy.packet import *

""" Based on the following references
 http://www.iana.org/assignments/igmp-type-numbers
 http://www.rfc-editor.org/rfc/pdfrfc/rfc3376.txt.pdf

"""

#  TODO: Merge IGMPv3 packet Bindlayers correct for 
#        membership source/Group records
#        ConditionalField parameters for IGMPv3 commented out
#
# See RFC3376, Section 4. Message Formats for definitions of proper IGMPv3 message format
#   http://www.faqs.org/rfcs/rfc3376.html
#
# See RFC4286, For definitions of proper messages for Multicast Router Discovery.
#   http://www.faqs.org/rfcs/rfc4286.html
#

#import sys, socket, struct, time
from scapy.all import *
print("IGMPv3  is still under development - Nov 2010")


class IGMPv3gr(Packet):
  """IGMP Group Record for IGMPv3 Membership Report

  This class is derived from class Packet and should be concatenated to an
  instantiation of class IGMPv3. Within the IGMPv3 instantiation, the numgrp
  element will need to be manipulated to indicate the proper number of
  group records.
  """
  name = "IGMPv3gr"
  igmpv3grtypes = { 1 : "Mode Is Include",
                    2 : "Mode Is Exclude",
                    3 : "Change To Include Mode",
                    4 : "Change To Exclude Mode",
                    5 : "Allow New Sources",
                    6 : "Block Old Sources"}

  fields_desc = [ ByteEnumField("rtype", 1, igmpv3grtypes),
                      ByteField("auxdlen",0),
                  FieldLenField("numsrc", None, "srcaddrs"),
                        IPField("maddr", "0.0.0.0"),
                 FieldListField("srcaddrs", None, IPField("sa", "0.0.0.0"), "numsrc") ]
  #show_indent=0
#--------------------------------------------------------------------------
  def post_build(self, p, pay):
    """Called implicitly before a packet is sent.
    """
    p += pay
    if self.auxdlen != 0:
      print("NOTICE: A properly formatted and complaint V3 Group Record should have an Auxiliary Data length of zero (0).")
      print("        Subsequent Group Records are lost!")
    return p
#--------------------------------------------------------------------------
  def mysummary(self):
    """Display a summary of the IGMPv3 group record."""
    return self.sprintf("IGMPv3 Group Record %IGMPv3gr.type% %IGMPv3gr.maddr%")


class IGMPv3(Packet):
  """IGMP Message Class for v3.

  This class is derived from class Packet. 
  The fields defined below are a 
  direct interpretation of the v3 Membership Query Message. 
  Fields 'type'  through 'qqic' are directly assignable. 
  For 'numsrc', do not assign a value. 
  Instead add to the 'srcaddrs' list to auto-set 'numsrc'. To 
  assign values to 'srcaddrs', use the following methods:
    c = IGMPv3()
    c.srcaddrs = ['1.2.3.4', '5.6.7.8']
    c.srcaddrs += ['192.168.10.24']
  At this point, 'c.numsrc' is three (3)

  'chksum' is automagically calculated before the packet is sent.

  'mrcode' is also the Advertisement Interval field

  """
  name = "IGMPv3"
  igmpv3types = { 0x11 : "Membership Query",
                  0x22 : "Version 3 Membership Report",
                  0x30 : "Multicast Router Advertisement",
                  0x31 : "Multicast Router Solicitation",
                  0x32 : "Multicast Router Termination"}

  fields_desc = [ ByteEnumField("type", 0x11, igmpv3types),
                      ByteField("mrcode",0),
                    XShortField("chksum", None),
                    IPField("gaddr", "0.0.0.0")
                    ]
                                          # use float_encode()

    # if type = 0x11 (Membership Query), the next field is group address 
    #   ConditionalField(IPField("gaddr", "0.0.0.0"), "type", lambda x:x==0x11),
    # else if type = 0x22 (Membership Report), the next fields are 
    #         reserved and number of group records
    #ConditionalField(ShortField("rsvd2", 0), "type", lambda x:x==0x22),
    #ConditionalField(ShortField("numgrp", 0), "type", lambda x:x==0x22),
#                  FieldLenField("numgrp", None, "grprecs")]
    # else if type = 0x30 (Multicast Router Advertisement), the next fields are 
    #         query interval and robustness
    #ConditionalField(ShortField("qryIntvl", 0), "type", lambda x:x==0x30),
    #ConditionalField(ShortField("robust", 0), "type", lambda x:x==0x30),
#  The following are only present for membership queries
       #   ConditionalField(BitField("resv", 0, 4), "type", lambda x:x==0x11),
       #   ConditionalField(BitField("s", 0, 1), "type", lambda x:x==0x11),
       #   ConditionalField(BitField("qrv", 0, 3), "type", lambda x:x==0x11), 
       #  ConditionalField(ByteField("qqic",0), "type", lambda x:x==0x11),
    # ConditionalField(FieldLenField("numsrc", None, "srcaddrs"), "type", lambda x:x==0x11),
    # ConditionalField(FieldListField("srcaddrs", None, IPField("sa", "0.0.0.0"), "numsrc"), "type", lambda x:x==0x11),

#--------------------------------------------------------------------------
  def float_encode(self, value):
    """Convert the integer value to its IGMPv3 encoded time value if needed.
   
    If value < 128, return the value specified. If >= 128, encode as a floating 
    point value. Value can be 0 - 31744.
    """
    if value < 128:
      code = value
    elif value > 31743:
      code = 255
    else:
      exp=0
      value>>=3
      while(value>31):
        exp+=1
        value>>=1
      exp<<=4
      code = 0x80 | exp | (value & 0x0F)
    return code

#--------------------------------------------------------------------------
  def post_build(self, p, pay):
    """Called implicitly before a packet is sent to compute and place IGMPv3 checksum.

    Parameters:
      self    The instantiation of an IGMPv3 class
      p       The IGMPv3 message in hex in network byte order
      pay     Additional payload for the IGMPv3 message
    """
    p += pay
    if self.type in [0, 0x31, 0x32, 0x22]:   # for these, field is reserved (0)
      p = p[:1]+chr(0)+p[2:]
    if self.chksum is None:
      ck = checksum(p)
      p = p[:2]+chr(ck>>8)+chr(ck&0xff)+p[4:]
    return p

#--------------------------------------------------------------------------
  def mysummary(self):
    """Display a summary of the IGMPv3 object."""

    if isinstance(self.underlayer, IP):
      return self.underlayer.sprintf("IGMPv3: %IP.src% > %IP.dst% %IGMPv3.type% %IGMPv3.gaddr%")
    else:
      return self.sprintf("IGMPv3 %IGMPv3.type% %IGMPv3.gaddr%")

#--------------------------------------------------------------------------
  def igmpize(self, ip=None, ether=None):
    """Called to explicitely fixup associated IP and Ethernet headers

    Parameters:
      self    The instantiation of an IGMP class.
      ip      The instantiation of the associated IP class.
      ether   The instantiation of the associated Ethernet.

    Returns:
      True    The tuple ether/ip/self passed all check and represents
               a proper IGMP packet.
      False   One of more validation checks failed and no fields 
               were adjusted.

    The function will examine the IGMP message to assure proper format. 
    Corrections will be attempted if possible. The IP header is then properly 
    adjusted to ensure correct formatting and assignment. The Ethernet header
    is then adjusted to the proper IGMP packet format.
    """

# The rules are:
#   1.  ttl = 1 (RFC 2236, section 2)
#  igmp_binds = [ (IP, IGMP,   { "proto": 2 , "ttl": 1 }),
#   2.  tos = 0xC0 (RFC 3376, section 4)
#               (IP, IGMPv3, { "proto": 2 , "ttl": 1, "tos":0xc0 }),
#               (IGMPv3, IGMPv3gr, { }) ]
# The rules are:
#   1.  the Max Response time is meaningful only in Membership Queries and should be zero 
#       otherwise (RFC 2236, section 2.2)

    if (self.type != 0x11):         #rule 1
      self.mrtime = 0
      
    if (self.adjust_ip(ip) == True):
      if (self.adjust_ether(ip, ether) == True): return True
    return False

#--------------------------------------------------------------------------
  def adjust_ether (self, ip=None, ether=None):
    """Called to explicitely fixup an associated Ethernet header

    The function adjusts the ethernet header destination MAC address based on 
    the destination IP address.
    """
# The rules are:
#   1. send to the group mac address address corresponding to the IP.dst
    if ip != None and ip.haslayer(IP) and ether != None and ether.haslayer(Ether):
      iplong = atol(ip.dst)
      ether.dst = "01:00:5e:%02x:%02x:%02x" % ( (iplong>>16)&0x7F, (iplong>>8)&0xFF, (iplong)&0xFF )
      # print "igmpize ip " + ip.dst + " as mac " + ether.dst 
      return True
    else:
      return False

#--------------------------------------------------------------------------
  def adjust_ip (self, ip=None):
    """Called to explicitely fixup an associated IP header

    The function adjusts the IP header based on conformance rules 
    and the group address encoded in the IGMP message.
    The rules are:
    1. Send General Group Query to 224.0.0.1 (all systems)
    2. Send Leave Group to 224.0.0.2 (all routers)
    3a.Otherwise send the packet to the group address
    3b.Send reports/joins to the group address
    4. ttl = 1 (RFC 2236, section 2)
    5. send the packet with the router alert IP option (RFC 2236, section 2)
    """
    if ip != None and ip.haslayer(IP):
      if (self.type == 0x11):
        if (self.gaddr == "0.0.0.0"):
          ip.dst = "224.0.0.1"                   # IP rule 1
          retCode = True                     
        elif isValidMCAddr(self.gaddr):
          ip.dst = self.gaddr                    # IP rule 3a
          retCode = True
        else:
          print("Warning: Using invalid Group Address")
          retCode = False
      elif ((self.type == 0x17) and isValidMCAddr(self.gaddr)):
          ip.dst = "224.0.0.2"                   # IP rule 2
          retCode = True
      elif ((self.type == 0x12) or (self.type == 0x16)) and (isValidMCAddr(self.gaddr)):
          ip.dst = self.gaddr                    # IP rule 3b
          retCode = True
      else:
        print("Warning: Using invalid IGMP Type")
        retCode = False
    else:
      print("Warning: No IGMP Group Address set")
      retCode = False
    if retCode == True:
       ip.ttl=1                                  # IP Rule 4
       ip.options=[IPOption_Router_Alert()]      # IP rule 5
    return retCode


bind_layers( IP,        IGMPv3,      frag=0, proto=2, ttl=1, tos=0xc0)
bind_layers( IGMPv3,    IGMPv3gr,    frag=0, proto=2)
bind_layers( IGMPv3gr,  IGMPv3gr,    frag=0, proto=2)