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 = EIGRP
     18 # scapy.contrib.status = loads
     19 
     20 """
     21     EIGRP Scapy Extension
     22     ~~~~~~~~~~~~~~~~~~~~~
     23 
     24     :version:   2009-08-13
     25     :copyright: 2009 by Jochen Bartl
     26     :e-mail:    lobo (at] c3a.de / jochen.bartl (at] gmail.com
     27     :license:   GPL v2
     28 
     29     :TODO
     30 
     31     - Replace TLV code with a more generic solution
     32         * http://trac.secdev.org/scapy/ticket/90
     33     - Write function for calculating authentication data
     34 
     35     :Known bugs:
     36 
     37         -
     38 
     39     :Thanks:
     40 
     41     - TLV code derived from the CDP implementation of scapy. (Thanks to Nicolas Bareil and Arnaud Ebalard)
     42         http://trac.secdev.org/scapy/ticket/18
     43     - IOS / EIGRP Version Representation FIX by Dirk Loss
     44 """
     45 
     46 from __future__ import absolute_import
     47 from scapy.packet import *
     48 from scapy.fields import *
     49 from scapy.layers.inet import IP
     50 from scapy.layers.inet6 import *
     51 from scapy.compat import chb, raw
     52 
     53 class EigrpIPField(StrField, IPField):
     54     """
     55     This is a special field type for handling ip addresses of destination networks in internal and
     56     external route updates.
     57 
     58     EIGRP removes zeros from the host portion of the ip address if the netmask is 8, 16 or 24 bits.
     59     """
     60 
     61     __slots__ = ["length_from"]
     62 
     63     def __init__(self, name, default, length=None, length_from=None):
     64         StrField.__init__(self, name, default)
     65         self.length_from  = length_from
     66         if length is not None:
     67             self.length_from = lambda pkt,length=length: length
     68 
     69     def h2i(self, pkt, x):
     70         return IPField.h2i(self, pkt, x)
     71 
     72     def i2m(self, pkt, x):
     73         x = inet_aton(x)
     74         l = self.length_from(pkt)
     75 
     76         if l <= 8:
     77             return x[:1]
     78         elif l <= 16:
     79             return x[:2]
     80         elif l <= 24:
     81             return x[:3]
     82         else:
     83             return x
     84 
     85     def m2i(self, pkt, x):
     86         l = self.length_from(pkt)
     87 
     88         if l <= 8:
     89             x += b"\x00\x00\x00"
     90         elif l <= 16:
     91             x += b"\x00\x00"
     92         elif l <= 24:
     93             x += b"\x00"
     94 
     95         return inet_ntoa(x)
     96 
     97     def prefixlen_to_bytelen(self, l):
     98         if l <= 8:
     99             l = 1
    100         elif l <= 16:
    101             l = 2
    102         elif l <= 24:
    103             l = 3
    104         else:
    105             l = 4
    106 
    107         return l
    108 
    109     def i2len(self, pkt, x):
    110         l = self.length_from(pkt)
    111         l = self.prefixlen_to_bytelen(l)
    112         return l
    113 
    114     def getfield(self, pkt, s):
    115         l = self.length_from(pkt)
    116         l = self.prefixlen_to_bytelen(l)
    117         return s[l:], self.m2i(pkt, s[:l])
    118 
    119     def randval(self):
    120         return IPField.randval(self)
    121 
    122 class EigrpIP6Field(StrField, IP6Field):
    123     """
    124     This is a special field type for handling ip addresses of destination networks in internal and
    125     external route updates.
    126 
    127     """
    128 
    129     __slots__ = ["length_from"]
    130 
    131     def __init__(self, name, default, length=None, length_from=None):
    132         StrField.__init__(self, name, default)
    133         self.length_from  = length_from
    134         if length is not None:
    135             self.length_from = lambda pkt,length=length: length
    136 
    137     def any2i(self, pkt, x):
    138         return IP6Field.any2i(self, pkt, x)
    139 
    140     def i2repr(self, pkt, x):
    141         return IP6Field.i2repr(self, pkt, x)
    142 
    143     def h2i(self, pkt, x):
    144         return IP6Field.h2i(self, pkt, x)
    145 
    146     def i2m(self, pkt, x):
    147         x = inet_pton(socket.AF_INET6, x)
    148         l = self.length_from(pkt)
    149         l = self.prefixlen_to_bytelen(l)
    150 
    151         return x[:l]
    152 
    153     def m2i(self, pkt, x):
    154         l = self.length_from(pkt)
    155 
    156         prefixlen = self.prefixlen_to_bytelen(l)
    157         if l > 128:
    158             warning("EigrpIP6Field: Prefix length is > 128. Dissection of this packet will fail")
    159         else:
    160             pad = b"\x00" * (16 - prefixlen)
    161             x += pad
    162 
    163         return inet_ntop(socket.AF_INET6, x)
    164 
    165     def prefixlen_to_bytelen(self, l):
    166         l = l // 8
    167 
    168         if l < 16:
    169             l += 1
    170 
    171         return l
    172 
    173     def i2len(self, pkt, x):
    174         l = self.length_from(pkt)
    175         l = self.prefixlen_to_bytelen(l)
    176         return l
    177 
    178     def getfield(self, pkt, s):
    179         l = self.length_from(pkt)
    180         l = self.prefixlen_to_bytelen(l)
    181         return s[l:], self.m2i(pkt, s[:l])
    182 
    183     def randval(self):
    184         return IP6Field.randval(self)
    185 
    186 class EIGRPGeneric(Packet):
    187     name = "EIGRP Generic TLV"
    188     fields_desc = [ XShortField("type", 0x0000),
    189             FieldLenField("len", None, "value", "!H", adjust=lambda pkt,x: x + 4),
    190             StrLenField("value", b"\x00", length_from=lambda pkt: pkt.len - 4)]
    191 
    192     def guess_payload_class(self, p):
    193         return conf.padding_layer
    194 
    195 class EIGRPParam(EIGRPGeneric):
    196     name = "EIGRP Parameters"
    197     fields_desc = [ XShortField("type", 0x0001),
    198             ShortField("len", 12),
    199             # Bandwidth
    200             ByteField("k1", 1),
    201             # Load
    202             ByteField("k2", 0),
    203             # Delay
    204             ByteField("k3", 1),
    205             # Reliability
    206             ByteField("k4", 0),
    207             # MTU
    208             ByteField("k5", 0),
    209             ByteField("reserved", 0),
    210             ShortField("holdtime", 15)
    211             ]
    212 
    213 class EIGRPAuthData(EIGRPGeneric):
    214     name = "EIGRP Authentication Data"
    215     fields_desc = [ XShortField("type", 0x0002),
    216             FieldLenField("len", None, "authdata", "!H", adjust=lambda pkt,x: x + 24),
    217             ShortEnumField("authtype", 2, {2 : "MD5"}),
    218             ShortField("keysize", None),
    219             IntField("keyid", 1),
    220             StrFixedLenField("nullpad", b"\x00" * 12, 12),
    221             StrLenField("authdata", RandString(16), length_from=lambda pkt: pkt.keysize)
    222             ]
    223 
    224     def post_build(self, p, pay):
    225         p += pay
    226 
    227         if self.keysize is None:
    228             keysize = len(self.authdata)
    229             p = p[:6] + chb((keysize >> 8) & 0xff) + chb(keysize & 0xff) + p[8:]
    230 
    231         return p
    232 
    233 class EIGRPSeq(EIGRPGeneric):
    234     name = "EIGRP Sequence"
    235     fields_desc = [ XShortField("type", 0x0003),
    236             ShortField("len", None),
    237             ByteField("addrlen", 4),
    238             ConditionalField(IPField("ipaddr", "192.168.0.1"),
    239                             lambda pkt:pkt.addrlen == 4),
    240             ConditionalField(IP6Field("ip6addr", "2001::"),
    241                             lambda pkt:pkt.addrlen == 16)
    242             ]
    243 
    244     def post_build(self, p, pay):
    245         p += pay
    246 
    247         if self.len is None:
    248             l = len(p)
    249             p = p[:2] + chb((l >> 8) & 0xff) + chb(l & 0xff) + p[4:]
    250 
    251         return p
    252 
    253 class ShortVersionField(ShortField):
    254     def i2repr(self, pkt, x):
    255         try:
    256             minor = x & 0xff
    257             major = (x >> 8) & 0xff
    258         except TypeError:
    259             return "unknown"
    260         else:
    261             # We print a leading 'v' so that these values don't look like floats
    262             return "v%s.%s" % (major, minor)
    263 
    264     def h2i(self, pkt, x):
    265         """The field accepts string values like v12.1, v1.1 or integer values.
    266            String values have to start with a "v" folled by a floating point number.
    267            Valid numbers are between 0 and 255.
    268         """
    269 
    270         if isinstance(x, str) and x.startswith("v") and len(x) <= 8:
    271             major = int(x.split(".")[0][1:])
    272             minor = int(x.split(".")[1])
    273 
    274             return (major << 8) | minor
    275 
    276         elif isinstance(x, int) and 0 <= x <= 65535:
    277             return x
    278         else:
    279             if self.default != None:
    280                 warning("set value to default. Format of %r is invalid" % x)
    281                 return self.default
    282             else:
    283                 raise Scapy_Exception("Format of value is invalid")
    284 
    285     def randval(self):
    286         return RandShort()
    287 
    288 class EIGRPSwVer(EIGRPGeneric):
    289     name = "EIGRP Software Version"
    290     fields_desc = [ XShortField("type", 0x0004),
    291             ShortField("len", 8),
    292             ShortVersionField("ios", "v12.0"),
    293             ShortVersionField("eigrp", "v1.2")
    294             ]
    295 
    296 class EIGRPNms(EIGRPGeneric):
    297     name = "EIGRP Next Multicast Sequence"
    298     fields_desc = [ XShortField("type", 0x0005),
    299             ShortField("len", 8),
    300             IntField("nms", 2)
    301             ]
    302 
    303 # Don't get confused by the term "receive-only". This flag is always set, when you configure
    304 # one of the stub options. It's also the only flag set, when you configure "eigrp stub receive-only".
    305 _EIGRP_STUB_FLAGS = ["connected", "static", "summary", "receive-only", "redistributed", "leak-map"]
    306 
    307 class EIGRPStub(EIGRPGeneric):
    308     name = "EIGRP Stub Router"
    309     fields_desc = [ XShortField("type", 0x0006),
    310             ShortField("len", 6),
    311             FlagsField("flags", 0x000d, 16, _EIGRP_STUB_FLAGS)]
    312 
    313 # Delay 0xffffffff == Destination Unreachable
    314 class EIGRPIntRoute(EIGRPGeneric):
    315     name = "EIGRP Internal Route"
    316     fields_desc = [ XShortField("type", 0x0102),
    317             FieldLenField("len", None, "dst", "!H", adjust=lambda pkt,x: x + 25),
    318             IPField("nexthop", "192.168.0.0"),
    319             IntField("delay", 128000),
    320             IntField("bandwidth", 256),
    321             ThreeBytesField("mtu", 1500),
    322             ByteField("hopcount", 0),
    323             ByteField("reliability", 255),
    324             ByteField("load", 0),
    325             XShortField("reserved", 0),
    326             ByteField("prefixlen", 24),
    327             EigrpIPField("dst", "192.168.1.0", length_from=lambda pkt: pkt.prefixlen),
    328             ]
    329 
    330 _EIGRP_EXTERNAL_PROTOCOL_ID = {
    331                             0x01 : "IGRP",
    332                             0x02 : "EIGRP",
    333                             0x03 : "Static Route",
    334                             0x04 : "RIP",
    335                             0x05 : "Hello",
    336                             0x06 : "OSPF",
    337                             0x07 : "IS-IS",
    338                             0x08 : "EGP",
    339                             0x09 : "BGP",
    340                             0x0A : "IDRP",
    341                             0x0B : "Connected Link"
    342                             }
    343 
    344 _EIGRP_EXTROUTE_FLAGS = ["external", "candidate-default"]
    345 
    346 class EIGRPExtRoute(EIGRPGeneric):
    347     name = "EIGRP External Route"
    348     fields_desc = [ XShortField("type", 0x0103),
    349             FieldLenField("len", None, "dst", "!H", adjust=lambda pkt,x: x + 45),
    350             IPField("nexthop", "192.168.0.0"),
    351             IPField("originrouter", "192.168.0.1"),
    352             IntField("originasn", 0),
    353             IntField("tag", 0),
    354             IntField("externalmetric", 0),
    355             ShortField("reserved", 0),
    356             ByteEnumField("extprotocolid", 3, _EIGRP_EXTERNAL_PROTOCOL_ID),
    357             FlagsField("flags", 0, 8, _EIGRP_EXTROUTE_FLAGS),
    358             IntField("delay", 0),
    359             IntField("bandwidth", 256),
    360             ThreeBytesField("mtu", 1500),
    361             ByteField("hopcount", 0),
    362             ByteField("reliability", 255),
    363             ByteField("load", 0),
    364             XShortField("reserved2", 0),
    365             ByteField("prefixlen", 24),
    366             EigrpIPField("dst", "192.168.1.0", length_from=lambda pkt: pkt.prefixlen)
    367             ]
    368 
    369 class EIGRPv6IntRoute(EIGRPGeneric):
    370     name = "EIGRP for IPv6 Internal Route"
    371     fields_desc = [ XShortField("type", 0x0402),
    372             FieldLenField("len", None, "dst", "!H", adjust=lambda pkt,x: x + 37),
    373             IP6Field("nexthop", "::"),
    374             IntField("delay", 128000),
    375             IntField("bandwidth", 256000),
    376             ThreeBytesField("mtu", 1500),
    377             ByteField("hopcount", 1),
    378             ByteField("reliability", 255),
    379             ByteField("load", 0),
    380             XShortField("reserved", 0),
    381             ByteField("prefixlen", 16),
    382             EigrpIP6Field("dst", "2001::", length_from=lambda pkt: pkt.prefixlen)
    383             ]
    384 
    385 class EIGRPv6ExtRoute(EIGRPGeneric):
    386     name = "EIGRP for IPv6 External Route"
    387     fields_desc = [ XShortField("type", 0x0403),
    388             FieldLenField("len", None, "dst", "!H", adjust=lambda pkt,x: x + 57),
    389             IP6Field("nexthop", "::"),
    390             IPField("originrouter", "192.168.0.1"),
    391             IntField("originasn", 0),
    392             IntField("tag", 0),
    393             IntField("externalmetric", 0),
    394             ShortField("reserved", 0),
    395             ByteEnumField("extprotocolid", 3, _EIGRP_EXTERNAL_PROTOCOL_ID),
    396             FlagsField("flags", 0, 8, _EIGRP_EXTROUTE_FLAGS),
    397             IntField("delay", 0),
    398             IntField("bandwidth", 256000),
    399             ThreeBytesField("mtu", 1500),
    400             ByteField("hopcount", 1),
    401             ByteField("reliability", 0),
    402             ByteField("load", 1),
    403             XShortField("reserved2", 0),
    404             ByteField("prefixlen", 8),
    405             EigrpIP6Field("dst", "::", length_from=lambda pkt: pkt.prefixlen)
    406             ]
    407 
    408 _eigrp_tlv_cls = {
    409                     0x0001: "EIGRPParam",
    410                     0x0002: "EIGRPAuthData",
    411                     0x0003: "EIGRPSeq",
    412                     0x0004: "EIGRPSwVer",
    413                     0x0005: "EIGRPNms",
    414                     0x0006: "EIGRPStub",
    415                     0x0102: "EIGRPIntRoute",
    416                     0x0103: "EIGRPExtRoute",
    417                     0x0402: "EIGRPv6IntRoute",
    418                     0x0403: "EIGRPv6ExtRoute"
    419                    }
    420 
    421 class RepeatedTlvListField(PacketListField):
    422     def __init__(self, name, default, cls):
    423         PacketField.__init__(self, name, default, cls)
    424 
    425     def getfield(self, pkt, s):
    426         lst = []
    427         remain = s
    428         while len(remain) > 0:
    429             p = self.m2i(pkt, remain)
    430             if conf.padding_layer in p:
    431                 pad = p[conf.padding_layer]
    432                 remain = pad.load
    433                 del(pad.underlayer.payload)
    434             else:
    435                 remain = b""
    436             lst.append(p)
    437         return remain,lst
    438 
    439     def addfield(self, pkt, s, val):
    440         return s + b"".join(raw(v) for v in val)
    441 
    442 def _EIGRPGuessPayloadClass(p, **kargs):
    443     cls = conf.raw_layer
    444     if len(p) >= 2:
    445         t = struct.unpack("!H", p[:2])[0]
    446         clsname = _eigrp_tlv_cls.get(t, "EIGRPGeneric")
    447         cls = globals()[clsname]
    448     return cls(p, **kargs)
    449 
    450 _EIGRP_OPCODES = { 1 : "Update",
    451                    2 : "Request",
    452                    3 : "Query",
    453                    4 : "Replay",
    454                    5 : "Hello",
    455                    6 : "IPX SAP",
    456                    10 : "SIA Query",
    457                    11 : "SIA Reply" }
    458 
    459 # The Conditional Receive bit is used for reliable multicast communication.
    460 # Update-Flag: Not sure if Cisco calls it that way, but it's set when neighbors
    461 # are exchanging routing information
    462 _EIGRP_FLAGS = ["init", "cond-recv", "unknown", "update"]
    463 
    464 class EIGRP(Packet):
    465     name = "EIGRP"
    466     fields_desc = [ ByteField("ver", 2),
    467                     ByteEnumField("opcode", 5, _EIGRP_OPCODES),
    468                     XShortField("chksum", None),
    469                     FlagsField("flags", 0, 32, _EIGRP_FLAGS),
    470                     IntField("seq", 0),
    471                     IntField("ack", 0),
    472                     IntField("asn", 100),
    473                     RepeatedTlvListField("tlvlist", [], _EIGRPGuessPayloadClass)
    474                  ]
    475 
    476     def post_build(self, p, pay):
    477         p += pay
    478         if self.chksum is None:
    479             c = checksum(p)
    480             p = p[:2] + chb((c >> 8) & 0xff) + chb(c & 0xff) + p[4:]
    481         return p
    482 
    483     def mysummary(self):
    484         summarystr = "EIGRP (AS=%EIGRP.asn% Opcode=%EIGRP.opcode%"
    485         if self.opcode == 5 and self.ack != 0:
    486             summarystr += " (ACK)"
    487         if self.flags != 0:
    488             summarystr += " Flags=%EIGRP.flags%"
    489 
    490         return self.sprintf(summarystr + ")")
    491 
    492 bind_layers(IP, EIGRP, proto=88)
    493 bind_layers(IPv6, EIGRP, nh=88)
    494 
    495 if __name__ == "__main__":
    496     from scapy.main import interact
    497     interact(mydict=globals(), mybanner="EIGRP")
    498 
    499