| #!/usr/bin/env python |
| |
| # This file is part of Scapy |
| # Scapy is free software: you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as published by |
| # the Free Software Foundation, either version 2 of the License, or |
| # any later version. |
| # |
| # Scapy is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with Scapy. If not, see <http://www.gnu.org/licenses/>. |
| |
| # scapy.contrib.description = EIGRP |
| # scapy.contrib.status = loads |
| |
| """ |
| EIGRP Scapy Extension |
| ~~~~~~~~~~~~~~~~~~~~~ |
| |
| :version: 2009-08-13 |
| :copyright: 2009 by Jochen Bartl |
| :e-mail: lobo@c3a.de / jochen.bartl@gmail.com |
| :license: GPL v2 |
| |
| :TODO |
| |
| - Replace TLV code with a more generic solution |
| * http://trac.secdev.org/scapy/ticket/90 |
| - Write function for calculating authentication data |
| |
| :Known bugs: |
| |
| - |
| |
| :Thanks: |
| |
| - TLV code derived from the CDP implementation of scapy. (Thanks to Nicolas Bareil and Arnaud Ebalard) |
| http://trac.secdev.org/scapy/ticket/18 |
| - IOS / EIGRP Version Representation FIX by Dirk Loss |
| """ |
| |
| from __future__ import absolute_import |
| from scapy.packet import * |
| from scapy.fields import * |
| from scapy.layers.inet import IP |
| from scapy.layers.inet6 import * |
| from scapy.compat import chb, raw |
| |
| class EigrpIPField(StrField, IPField): |
| """ |
| This is a special field type for handling ip addresses of destination networks in internal and |
| external route updates. |
| |
| EIGRP removes zeros from the host portion of the ip address if the netmask is 8, 16 or 24 bits. |
| """ |
| |
| __slots__ = ["length_from"] |
| |
| def __init__(self, name, default, length=None, length_from=None): |
| StrField.__init__(self, name, default) |
| self.length_from = length_from |
| if length is not None: |
| self.length_from = lambda pkt,length=length: length |
| |
| def h2i(self, pkt, x): |
| return IPField.h2i(self, pkt, x) |
| |
| def i2m(self, pkt, x): |
| x = inet_aton(x) |
| l = self.length_from(pkt) |
| |
| if l <= 8: |
| return x[:1] |
| elif l <= 16: |
| return x[:2] |
| elif l <= 24: |
| return x[:3] |
| else: |
| return x |
| |
| def m2i(self, pkt, x): |
| l = self.length_from(pkt) |
| |
| if l <= 8: |
| x += b"\x00\x00\x00" |
| elif l <= 16: |
| x += b"\x00\x00" |
| elif l <= 24: |
| x += b"\x00" |
| |
| return inet_ntoa(x) |
| |
| def prefixlen_to_bytelen(self, l): |
| if l <= 8: |
| l = 1 |
| elif l <= 16: |
| l = 2 |
| elif l <= 24: |
| l = 3 |
| else: |
| l = 4 |
| |
| return l |
| |
| def i2len(self, pkt, x): |
| l = self.length_from(pkt) |
| l = self.prefixlen_to_bytelen(l) |
| return l |
| |
| def getfield(self, pkt, s): |
| l = self.length_from(pkt) |
| l = self.prefixlen_to_bytelen(l) |
| return s[l:], self.m2i(pkt, s[:l]) |
| |
| def randval(self): |
| return IPField.randval(self) |
| |
| class EigrpIP6Field(StrField, IP6Field): |
| """ |
| This is a special field type for handling ip addresses of destination networks in internal and |
| external route updates. |
| |
| """ |
| |
| __slots__ = ["length_from"] |
| |
| def __init__(self, name, default, length=None, length_from=None): |
| StrField.__init__(self, name, default) |
| self.length_from = length_from |
| if length is not None: |
| self.length_from = lambda pkt,length=length: length |
| |
| def any2i(self, pkt, x): |
| return IP6Field.any2i(self, pkt, x) |
| |
| def i2repr(self, pkt, x): |
| return IP6Field.i2repr(self, pkt, x) |
| |
| def h2i(self, pkt, x): |
| return IP6Field.h2i(self, pkt, x) |
| |
| def i2m(self, pkt, x): |
| x = inet_pton(socket.AF_INET6, x) |
| l = self.length_from(pkt) |
| l = self.prefixlen_to_bytelen(l) |
| |
| return x[:l] |
| |
| def m2i(self, pkt, x): |
| l = self.length_from(pkt) |
| |
| prefixlen = self.prefixlen_to_bytelen(l) |
| if l > 128: |
| warning("EigrpIP6Field: Prefix length is > 128. Dissection of this packet will fail") |
| else: |
| pad = b"\x00" * (16 - prefixlen) |
| x += pad |
| |
| return inet_ntop(socket.AF_INET6, x) |
| |
| def prefixlen_to_bytelen(self, l): |
| l = l // 8 |
| |
| if l < 16: |
| l += 1 |
| |
| return l |
| |
| def i2len(self, pkt, x): |
| l = self.length_from(pkt) |
| l = self.prefixlen_to_bytelen(l) |
| return l |
| |
| def getfield(self, pkt, s): |
| l = self.length_from(pkt) |
| l = self.prefixlen_to_bytelen(l) |
| return s[l:], self.m2i(pkt, s[:l]) |
| |
| def randval(self): |
| return IP6Field.randval(self) |
| |
| class EIGRPGeneric(Packet): |
| name = "EIGRP Generic TLV" |
| fields_desc = [ XShortField("type", 0x0000), |
| FieldLenField("len", None, "value", "!H", adjust=lambda pkt,x: x + 4), |
| StrLenField("value", b"\x00", length_from=lambda pkt: pkt.len - 4)] |
| |
| def guess_payload_class(self, p): |
| return conf.padding_layer |
| |
| class EIGRPParam(EIGRPGeneric): |
| name = "EIGRP Parameters" |
| fields_desc = [ XShortField("type", 0x0001), |
| ShortField("len", 12), |
| # Bandwidth |
| ByteField("k1", 1), |
| # Load |
| ByteField("k2", 0), |
| # Delay |
| ByteField("k3", 1), |
| # Reliability |
| ByteField("k4", 0), |
| # MTU |
| ByteField("k5", 0), |
| ByteField("reserved", 0), |
| ShortField("holdtime", 15) |
| ] |
| |
| class EIGRPAuthData(EIGRPGeneric): |
| name = "EIGRP Authentication Data" |
| fields_desc = [ XShortField("type", 0x0002), |
| FieldLenField("len", None, "authdata", "!H", adjust=lambda pkt,x: x + 24), |
| ShortEnumField("authtype", 2, {2 : "MD5"}), |
| ShortField("keysize", None), |
| IntField("keyid", 1), |
| StrFixedLenField("nullpad", b"\x00" * 12, 12), |
| StrLenField("authdata", RandString(16), length_from=lambda pkt: pkt.keysize) |
| ] |
| |
| def post_build(self, p, pay): |
| p += pay |
| |
| if self.keysize is None: |
| keysize = len(self.authdata) |
| p = p[:6] + chb((keysize >> 8) & 0xff) + chb(keysize & 0xff) + p[8:] |
| |
| return p |
| |
| class EIGRPSeq(EIGRPGeneric): |
| name = "EIGRP Sequence" |
| fields_desc = [ XShortField("type", 0x0003), |
| ShortField("len", None), |
| ByteField("addrlen", 4), |
| ConditionalField(IPField("ipaddr", "192.168.0.1"), |
| lambda pkt:pkt.addrlen == 4), |
| ConditionalField(IP6Field("ip6addr", "2001::"), |
| lambda pkt:pkt.addrlen == 16) |
| ] |
| |
| def post_build(self, p, pay): |
| p += pay |
| |
| if self.len is None: |
| l = len(p) |
| p = p[:2] + chb((l >> 8) & 0xff) + chb(l & 0xff) + p[4:] |
| |
| return p |
| |
| class ShortVersionField(ShortField): |
| def i2repr(self, pkt, x): |
| try: |
| minor = x & 0xff |
| major = (x >> 8) & 0xff |
| except TypeError: |
| return "unknown" |
| else: |
| # We print a leading 'v' so that these values don't look like floats |
| return "v%s.%s" % (major, minor) |
| |
| def h2i(self, pkt, x): |
| """The field accepts string values like v12.1, v1.1 or integer values. |
| String values have to start with a "v" folled by a floating point number. |
| Valid numbers are between 0 and 255. |
| """ |
| |
| if isinstance(x, str) and x.startswith("v") and len(x) <= 8: |
| major = int(x.split(".")[0][1:]) |
| minor = int(x.split(".")[1]) |
| |
| return (major << 8) | minor |
| |
| elif isinstance(x, int) and 0 <= x <= 65535: |
| return x |
| else: |
| if self.default != None: |
| warning("set value to default. Format of %r is invalid" % x) |
| return self.default |
| else: |
| raise Scapy_Exception("Format of value is invalid") |
| |
| def randval(self): |
| return RandShort() |
| |
| class EIGRPSwVer(EIGRPGeneric): |
| name = "EIGRP Software Version" |
| fields_desc = [ XShortField("type", 0x0004), |
| ShortField("len", 8), |
| ShortVersionField("ios", "v12.0"), |
| ShortVersionField("eigrp", "v1.2") |
| ] |
| |
| class EIGRPNms(EIGRPGeneric): |
| name = "EIGRP Next Multicast Sequence" |
| fields_desc = [ XShortField("type", 0x0005), |
| ShortField("len", 8), |
| IntField("nms", 2) |
| ] |
| |
| # Don't get confused by the term "receive-only". This flag is always set, when you configure |
| # one of the stub options. It's also the only flag set, when you configure "eigrp stub receive-only". |
| _EIGRP_STUB_FLAGS = ["connected", "static", "summary", "receive-only", "redistributed", "leak-map"] |
| |
| class EIGRPStub(EIGRPGeneric): |
| name = "EIGRP Stub Router" |
| fields_desc = [ XShortField("type", 0x0006), |
| ShortField("len", 6), |
| FlagsField("flags", 0x000d, 16, _EIGRP_STUB_FLAGS)] |
| |
| # Delay 0xffffffff == Destination Unreachable |
| class EIGRPIntRoute(EIGRPGeneric): |
| name = "EIGRP Internal Route" |
| fields_desc = [ XShortField("type", 0x0102), |
| FieldLenField("len", None, "dst", "!H", adjust=lambda pkt,x: x + 25), |
| IPField("nexthop", "192.168.0.0"), |
| IntField("delay", 128000), |
| IntField("bandwidth", 256), |
| ThreeBytesField("mtu", 1500), |
| ByteField("hopcount", 0), |
| ByteField("reliability", 255), |
| ByteField("load", 0), |
| XShortField("reserved", 0), |
| ByteField("prefixlen", 24), |
| EigrpIPField("dst", "192.168.1.0", length_from=lambda pkt: pkt.prefixlen), |
| ] |
| |
| _EIGRP_EXTERNAL_PROTOCOL_ID = { |
| 0x01 : "IGRP", |
| 0x02 : "EIGRP", |
| 0x03 : "Static Route", |
| 0x04 : "RIP", |
| 0x05 : "Hello", |
| 0x06 : "OSPF", |
| 0x07 : "IS-IS", |
| 0x08 : "EGP", |
| 0x09 : "BGP", |
| 0x0A : "IDRP", |
| 0x0B : "Connected Link" |
| } |
| |
| _EIGRP_EXTROUTE_FLAGS = ["external", "candidate-default"] |
| |
| class EIGRPExtRoute(EIGRPGeneric): |
| name = "EIGRP External Route" |
| fields_desc = [ XShortField("type", 0x0103), |
| FieldLenField("len", None, "dst", "!H", adjust=lambda pkt,x: x + 45), |
| IPField("nexthop", "192.168.0.0"), |
| IPField("originrouter", "192.168.0.1"), |
| IntField("originasn", 0), |
| IntField("tag", 0), |
| IntField("externalmetric", 0), |
| ShortField("reserved", 0), |
| ByteEnumField("extprotocolid", 3, _EIGRP_EXTERNAL_PROTOCOL_ID), |
| FlagsField("flags", 0, 8, _EIGRP_EXTROUTE_FLAGS), |
| IntField("delay", 0), |
| IntField("bandwidth", 256), |
| ThreeBytesField("mtu", 1500), |
| ByteField("hopcount", 0), |
| ByteField("reliability", 255), |
| ByteField("load", 0), |
| XShortField("reserved2", 0), |
| ByteField("prefixlen", 24), |
| EigrpIPField("dst", "192.168.1.0", length_from=lambda pkt: pkt.prefixlen) |
| ] |
| |
| class EIGRPv6IntRoute(EIGRPGeneric): |
| name = "EIGRP for IPv6 Internal Route" |
| fields_desc = [ XShortField("type", 0x0402), |
| FieldLenField("len", None, "dst", "!H", adjust=lambda pkt,x: x + 37), |
| IP6Field("nexthop", "::"), |
| IntField("delay", 128000), |
| IntField("bandwidth", 256000), |
| ThreeBytesField("mtu", 1500), |
| ByteField("hopcount", 1), |
| ByteField("reliability", 255), |
| ByteField("load", 0), |
| XShortField("reserved", 0), |
| ByteField("prefixlen", 16), |
| EigrpIP6Field("dst", "2001::", length_from=lambda pkt: pkt.prefixlen) |
| ] |
| |
| class EIGRPv6ExtRoute(EIGRPGeneric): |
| name = "EIGRP for IPv6 External Route" |
| fields_desc = [ XShortField("type", 0x0403), |
| FieldLenField("len", None, "dst", "!H", adjust=lambda pkt,x: x + 57), |
| IP6Field("nexthop", "::"), |
| IPField("originrouter", "192.168.0.1"), |
| IntField("originasn", 0), |
| IntField("tag", 0), |
| IntField("externalmetric", 0), |
| ShortField("reserved", 0), |
| ByteEnumField("extprotocolid", 3, _EIGRP_EXTERNAL_PROTOCOL_ID), |
| FlagsField("flags", 0, 8, _EIGRP_EXTROUTE_FLAGS), |
| IntField("delay", 0), |
| IntField("bandwidth", 256000), |
| ThreeBytesField("mtu", 1500), |
| ByteField("hopcount", 1), |
| ByteField("reliability", 0), |
| ByteField("load", 1), |
| XShortField("reserved2", 0), |
| ByteField("prefixlen", 8), |
| EigrpIP6Field("dst", "::", length_from=lambda pkt: pkt.prefixlen) |
| ] |
| |
| _eigrp_tlv_cls = { |
| 0x0001: "EIGRPParam", |
| 0x0002: "EIGRPAuthData", |
| 0x0003: "EIGRPSeq", |
| 0x0004: "EIGRPSwVer", |
| 0x0005: "EIGRPNms", |
| 0x0006: "EIGRPStub", |
| 0x0102: "EIGRPIntRoute", |
| 0x0103: "EIGRPExtRoute", |
| 0x0402: "EIGRPv6IntRoute", |
| 0x0403: "EIGRPv6ExtRoute" |
| } |
| |
| class RepeatedTlvListField(PacketListField): |
| def __init__(self, name, default, cls): |
| PacketField.__init__(self, name, default, cls) |
| |
| def getfield(self, pkt, s): |
| lst = [] |
| remain = s |
| while len(remain) > 0: |
| p = self.m2i(pkt, remain) |
| if conf.padding_layer in p: |
| pad = p[conf.padding_layer] |
| remain = pad.load |
| del(pad.underlayer.payload) |
| else: |
| remain = b"" |
| lst.append(p) |
| return remain,lst |
| |
| def addfield(self, pkt, s, val): |
| return s + b"".join(raw(v) for v in val) |
| |
| def _EIGRPGuessPayloadClass(p, **kargs): |
| cls = conf.raw_layer |
| if len(p) >= 2: |
| t = struct.unpack("!H", p[:2])[0] |
| clsname = _eigrp_tlv_cls.get(t, "EIGRPGeneric") |
| cls = globals()[clsname] |
| return cls(p, **kargs) |
| |
| _EIGRP_OPCODES = { 1 : "Update", |
| 2 : "Request", |
| 3 : "Query", |
| 4 : "Replay", |
| 5 : "Hello", |
| 6 : "IPX SAP", |
| 10 : "SIA Query", |
| 11 : "SIA Reply" } |
| |
| # The Conditional Receive bit is used for reliable multicast communication. |
| # Update-Flag: Not sure if Cisco calls it that way, but it's set when neighbors |
| # are exchanging routing information |
| _EIGRP_FLAGS = ["init", "cond-recv", "unknown", "update"] |
| |
| class EIGRP(Packet): |
| name = "EIGRP" |
| fields_desc = [ ByteField("ver", 2), |
| ByteEnumField("opcode", 5, _EIGRP_OPCODES), |
| XShortField("chksum", None), |
| FlagsField("flags", 0, 32, _EIGRP_FLAGS), |
| IntField("seq", 0), |
| IntField("ack", 0), |
| IntField("asn", 100), |
| RepeatedTlvListField("tlvlist", [], _EIGRPGuessPayloadClass) |
| ] |
| |
| def post_build(self, p, pay): |
| p += pay |
| if self.chksum is None: |
| c = checksum(p) |
| p = p[:2] + chb((c >> 8) & 0xff) + chb(c & 0xff) + p[4:] |
| return p |
| |
| def mysummary(self): |
| summarystr = "EIGRP (AS=%EIGRP.asn% Opcode=%EIGRP.opcode%" |
| if self.opcode == 5 and self.ack != 0: |
| summarystr += " (ACK)" |
| if self.flags != 0: |
| summarystr += " Flags=%EIGRP.flags%" |
| |
| return self.sprintf(summarystr + ")") |
| |
| bind_layers(IP, EIGRP, proto=88) |
| bind_layers(IPv6, EIGRP, nh=88) |
| |
| if __name__ == "__main__": |
| from scapy.main import interact |
| interact(mydict=globals(), mybanner="EIGRP") |
| |