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 = ICMP Extensions
     18 # scapy.contrib.status = loads
     19 
     20 from __future__ import absolute_import
     21 import scapy
     22 from scapy.packet import Packet, bind_layers
     23 from scapy.fields import *
     24 from scapy.layers.inet import IP, ICMP
     25 from scapy.layers.inet6 import IP6Field
     26 from scapy.error import warning
     27 from scapy.contrib.mpls import MPLS
     28 import scapy.modules.six as six
     29 
     30 
     31 class ICMPExtensionObject(Packet):
     32     name = 'ICMP Extension Object'
     33     fields_desc = [ ShortField('len', None),
     34                     ByteField('classnum', 0),
     35                     ByteField('classtype', 0) ]
     36 
     37     def post_build(self, p, pay):
     38         if self.len is None:
     39             l = len(p)+len(pay)
     40             p = struct.pack('!H', l)+p[2:]
     41         return p+pay
     42 
     43 
     44 class ICMPExtensionHeader(Packet):
     45     name = 'ICMP Extension Header (RFC4884)'
     46     fields_desc = [ BitField('version', 2, 4),
     47                     BitField('reserved', 0, 12),
     48                     BitField('chksum', None, 16) ]
     49 
     50     _min_ieo_len = len(ICMPExtensionObject())
     51 
     52     def post_build(self, p, pay):
     53         if self.chksum is None:
     54             ck = checksum(p)
     55             p = p[:2]+chr(ck>>8)+chr(ck&0xff)+p[4:]
     56         return p+pay
     57 
     58     def guess_payload_class(self, payload):
     59         if len(payload) < self._min_ieo_len:
     60             return Packet.guess_payload_class(self, payload)
     61 
     62         # Look at fields of the generic ICMPExtensionObject to determine which
     63         # bound extension type to use.
     64         ieo = ICMPExtensionObject(payload)
     65         if ieo.len < self._min_ieo_len:
     66             return Packet.guess_payload_class(self, payload)
     67 
     68         for fval, cls in self.payload_guess:
     69             ok = 1
     70             for k, v in six.iteritems(fval):
     71                 if not hasattr(ieo, k) or v != ieo.getfieldval(k):
     72                     ok = 0
     73                     break
     74             if ok:
     75                 return cls
     76         return ICMPExtensionObject
     77 
     78 
     79 def ICMPExtension_post_dissection(self, pkt):
     80     # RFC4884 section 5.2 says if the ICMP packet length
     81     # is >144 then ICMP extensions start at byte 137.
     82 
     83     lastlayer = pkt.lastlayer()
     84     if not isinstance(lastlayer, conf.padding_layer):
     85       return
     86 
     87     if IP in pkt:
     88         if ( ICMP in pkt and
     89              pkt[ICMP].type in [3,11,12] and
     90              pkt.len > 144 ):
     91             bytes = pkt[ICMP].build()[136:]
     92         else:
     93             return
     94     elif scapy.layers.inet6.IPv6 in pkt:
     95         if ( (scapy.layers.inet6.ICMPv6TimeExceeded in pkt or
     96               scapy.layers.inet6.ICMPv6DestUnreach in pkt) and
     97               pkt.plen > 144 ):
     98             bytes = pkt[scapy.layers.inet6.ICMPv6TimeExceeded].build()[136:]
     99         else:
    100             return
    101     else:
    102         return
    103 
    104     # validate checksum
    105     ieh = ICMPExtensionHeader(bytes)
    106     if checksum(ieh.build()):
    107         return  # failed
    108 
    109     lastlayer.load = lastlayer.load[:-len(ieh)]
    110     lastlayer.add_payload(ieh)
    111 
    112 
    113 class ICMPExtensionMPLS(ICMPExtensionObject):
    114     name = 'ICMP Extension Object - MPLS (RFC4950)'
    115 
    116     fields_desc = [ ShortField('len', None),
    117                     ByteField('classnum', 1),
    118                     ByteField('classtype', 1),
    119                     PacketListField('stack', [], MPLS,
    120                                     length_from=lambda pkt: pkt.len - 4) ]
    121 
    122 
    123 class ICMPExtensionInterfaceInformation(ICMPExtensionObject):
    124     name = 'ICMP Extension Object - Interface Information Object (RFC5837)'
    125 
    126     fields_desc = [ ShortField('len', None),
    127                     ByteField('classnum', 2),
    128                     BitField('interface_role', 0, 2),
    129                     BitField('reserved', 0, 2),
    130                     BitField('has_ifindex', 0, 1),
    131                     BitField('has_ipaddr', 0, 1),
    132                     BitField('has_ifname', 0, 1),
    133                     BitField('has_mtu', 0, 1),
    134 
    135                     ConditionalField(
    136                         IntField('ifindex', None),
    137                         lambda pkt: pkt.has_ifindex == 1),
    138 
    139                     ConditionalField(
    140                         ShortField('afi', None),
    141                         lambda pkt: pkt.has_ipaddr == 1),
    142                     ConditionalField(
    143                         ShortField('reserved2', 0),
    144                         lambda pkt: pkt.has_ipaddr == 1),
    145                     ConditionalField(
    146                         IPField('ip4', None),
    147                         lambda pkt: pkt.afi == 1),
    148                     ConditionalField(
    149                         IP6Field('ip6', None),
    150                         lambda pkt: pkt.afi == 2),
    151 
    152                     ConditionalField(
    153                         FieldLenField('ifname_len', None, fmt='B',
    154                                       length_of='ifname'),
    155                         lambda pkt: pkt.has_ifname == 1),
    156                     ConditionalField(
    157                         StrLenField('ifname', None,
    158                                     length_from=lambda pkt: pkt.ifname_len),
    159                         lambda pkt: pkt.has_ifname == 1),
    160 
    161                     ConditionalField(
    162                         IntField('mtu', None),
    163                         lambda pkt: pkt.has_mtu == 1) ]
    164 
    165     def self_build(self, field_pos_list=None):
    166         if self.afi is None:
    167             if self.ip4 is not None:
    168                 self.afi = 1
    169             elif self.ip6 is not None:
    170                 self.afi = 2
    171 
    172         if self.has_ifindex and self.ifindex is None:
    173             warning('has_ifindex set but ifindex is not set.')
    174         if self.has_ipaddr and self.afi is None:
    175             warning('has_ipaddr set but afi is not set.')
    176         if self.has_ipaddr and self.ip4 is None and self.ip6 is None:
    177             warning('has_ipaddr set but ip4 or ip6 is not set.')
    178         if self.has_ifname and self.ifname is None:
    179             warning('has_ifname set but ifname is not set.')
    180         if self.has_mtu and self.mtu is None:
    181             warning('has_mtu set but mtu is not set.')
    182 
    183         return ICMPExtensionObject.self_build(self, field_pos_list=field_pos_list)
    184 
    185 
    186 # Add the post_dissection() method to the existing ICMPv4 and
    187 # ICMPv6 error messages
    188 scapy.layers.inet.ICMPerror.post_dissection = ICMPExtension_post_dissection
    189 scapy.layers.inet.TCPerror.post_dissection = ICMPExtension_post_dissection
    190 scapy.layers.inet.UDPerror.post_dissection = ICMPExtension_post_dissection
    191 
    192 scapy.layers.inet6.ICMPv6DestUnreach.post_dissection = ICMPExtension_post_dissection
    193 scapy.layers.inet6.ICMPv6TimeExceeded.post_dissection = ICMPExtension_post_dissection
    194 
    195 
    196 # ICMPExtensionHeader looks at fields from the upper layer object when
    197 # determining which upper layer to use.
    198 bind_layers(ICMPExtensionHeader, ICMPExtensionMPLS,                 classnum=1, classtype=1)
    199 bind_layers(ICMPExtensionHeader, ICMPExtensionInterfaceInformation, classnum=2)
    200