|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +# scapy.contrib.description = VLAN Trunking Protocol (VTP) |
| 4 | +# scapy.contrib.status = loads |
| 5 | + |
| 6 | +""" |
| 7 | + VTP Scapy Extension |
| 8 | + ~~~~~~~~~~~~~~~~~~~~~ |
| 9 | +
|
| 10 | + :version: 2009-02-15 |
| 11 | + :copyright: 2009 by Jochen Bartl |
| 12 | + :e-mail: lobo@c3a.de / jochen.bartl@gmail.com |
| 13 | + :license: GPL v2 |
| 14 | +
|
| 15 | + This program is free software; you can redistribute it and/or |
| 16 | + modify it under the terms of the GNU General Public License |
| 17 | + as published by the Free Software Foundation; either version 2 |
| 18 | + of the License, or (at your option) any later version. |
| 19 | +
|
| 20 | + This program is distributed in the hope that it will be useful, |
| 21 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 22 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 23 | + GNU General Public License for more details. |
| 24 | +
|
| 25 | + :TODO |
| 26 | +
|
| 27 | + - Join messages |
| 28 | + - RE MD5 hash calculation |
| 29 | + - Have a closer look at 8 byte padding in summary adv. |
| 30 | + "debug sw-vlan vtp packets" sais the TLV length is invalid, |
| 31 | + when I change the values |
| 32 | + '\x00\x00\x00\x01\x06\x01\x00\x02' |
| 33 | + * \x00\x00 ? |
| 34 | + * \x00\x01 tlvtype? |
| 35 | + * \x06 length? |
| 36 | + * \x00\x02 value? |
| 37 | + - h2i function for VTPTimeStampField |
| 38 | +
|
| 39 | + :References: |
| 40 | +
|
| 41 | + - Understanding VLAN Trunk Protocol (VTP) |
| 42 | + http://www.cisco.com/en/US/tech/tk389/tk689/technologies_tech_note09186a0080094c52.shtml |
| 43 | +""" |
| 44 | + |
| 45 | +from scapy.all import * |
| 46 | + |
| 47 | +_VTP_VLAN_TYPE = { |
| 48 | + 1 : 'Ethernet', |
| 49 | + 2 : 'FDDI', |
| 50 | + 3 : 'TrCRF', |
| 51 | + 4 : 'FDDI-net', |
| 52 | + 5 : 'TrBRF' |
| 53 | + } |
| 54 | + |
| 55 | +_VTP_VLANINFO_TLV_TYPE = { |
| 56 | + 0x01 : 'Source-Routing Ring Number', |
| 57 | + 0x02 : 'Source-Routing Bridge Number', |
| 58 | + 0x03 : 'Spanning-Tree Protocol Type', |
| 59 | + 0x04 : 'Parent VLAN', |
| 60 | + 0x05 : 'Translationally Bridged VLANs', |
| 61 | + 0x06 : 'Pruning', |
| 62 | + 0x07 : 'Bridge Type', |
| 63 | + 0x08 : 'Max ARE Hop Count', |
| 64 | + 0x09 : 'Max STE Hop Count', |
| 65 | + 0x0A : 'Backup CRF Mode' |
| 66 | + } |
| 67 | + |
| 68 | + |
| 69 | +class VTPVlanInfoTlv(Packet): |
| 70 | + name = "VTP VLAN Info TLV" |
| 71 | + fields_desc = [ |
| 72 | + ByteEnumField("type", 0, _VTP_VLANINFO_TLV_TYPE), |
| 73 | + ByteField("length", 0), |
| 74 | + StrLenField("value", None, length_from=lambda pkt : pkt.length + 1) |
| 75 | + ] |
| 76 | + |
| 77 | + def guess_payload_class(self, p): |
| 78 | + return Padding |
| 79 | + |
| 80 | +class VTPVlanInfo(Packet): |
| 81 | + name = "VTP VLAN Info" |
| 82 | + fields_desc = [ |
| 83 | + ByteField("len", None), # FIXME: compute length |
| 84 | + ByteEnumField("status", 0, {0 : "active", 1 : "suspended"}), |
| 85 | + ByteEnumField("type", 1, _VTP_VLAN_TYPE), |
| 86 | + FieldLenField("vlannamelen", None, "vlanname", "B"), |
| 87 | + ShortField("vlanid", 1), |
| 88 | + ShortField("mtu", 1500), |
| 89 | + XIntField("dot10index", None), |
| 90 | + StrLenField("vlanname", "default", length_from=lambda pkt:4 * ((pkt.vlannamelen + 3) / 4)), |
| 91 | + ConditionalField(PacketListField("tlvlist", [], VTPVlanInfoTlv, |
| 92 | + length_from=lambda pkt:pkt.len - 12 - (4 * ((pkt.vlannamelen + 3) / 4))), |
| 93 | + lambda pkt:pkt.type not in [1, 2]) |
| 94 | + ] |
| 95 | + |
| 96 | + def post_build(self, p, pay): |
| 97 | + vlannamelen = 4 * ((len(self.vlanname) + 3) / 4) |
| 98 | + |
| 99 | + if self.len == None: |
| 100 | + l = vlannamelen + 12 |
| 101 | + p = chr(l & 0xff) + p[1:] |
| 102 | + |
| 103 | + # Pad vlan name with zeros if vlannamelen > len(vlanname) |
| 104 | + l = vlannamelen - len(self.vlanname) |
| 105 | + if l != 0: |
| 106 | + p += "\x00" * l |
| 107 | + |
| 108 | + p += pay |
| 109 | + |
| 110 | + return p |
| 111 | + |
| 112 | + def guess_payload_class(self, p): |
| 113 | + return Padding |
| 114 | + |
| 115 | +_VTP_Types = { |
| 116 | + 1 : 'Summary Advertisement', |
| 117 | + 2 : 'Subset Advertisements', |
| 118 | + 3 : 'Advertisement Request', |
| 119 | + 4 : 'Join' |
| 120 | + } |
| 121 | + |
| 122 | +class VTPTimeStampField(StrFixedLenField): |
| 123 | + def __init__(self, name, default): |
| 124 | + StrFixedLenField.__init__(self, name, default, 12) |
| 125 | + |
| 126 | + def i2repr(self, pkt, x): |
| 127 | + return "%s-%s-%s %s:%s:%s" % (x[:2], x[2:4], x[4:6], x[6:8], x[8:10], x[10:12]) |
| 128 | + |
| 129 | +class VTP(Packet): |
| 130 | + name = "VTP" |
| 131 | + fields_desc = [ |
| 132 | + ByteField("ver", 2), |
| 133 | + ByteEnumField("code", 1, _VTP_Types), |
| 134 | + ConditionalField(ByteField("followers", 1), |
| 135 | + lambda pkt:pkt.code == 1), |
| 136 | + ConditionalField(ByteField("seq", 1), |
| 137 | + lambda pkt:pkt.code == 2), |
| 138 | + ConditionalField(ByteField("reserved", 0), |
| 139 | + lambda pkt:pkt.code == 3), |
| 140 | + ByteField("domnamelen", None), |
| 141 | + StrFixedLenField("domname", "manbearpig", 32), |
| 142 | + ConditionalField(SignedIntField("rev", 0), |
| 143 | + lambda pkt:pkt.code == 1 or |
| 144 | + pkt.code == 2), |
| 145 | + # updater identity |
| 146 | + ConditionalField(IPField("uid", "192.168.0.1"), |
| 147 | + lambda pkt:pkt.code == 1), |
| 148 | + ConditionalField(VTPTimeStampField("timestamp", '930301000000'), |
| 149 | + lambda pkt:pkt.code == 1), |
| 150 | + ConditionalField(StrFixedLenField("md5", "\x00" * 16, 16), |
| 151 | + lambda pkt:pkt.code == 1), |
| 152 | + ConditionalField( |
| 153 | + PacketListField("vlaninfo", [], VTPVlanInfo), |
| 154 | + lambda pkt: pkt.code == 2), |
| 155 | + ConditionalField(ShortField("startvalue", 0), |
| 156 | + lambda pkt:pkt.code == 3) |
| 157 | + ] |
| 158 | + |
| 159 | + def post_build(self, p, pay): |
| 160 | + if self.domnamelen == None: |
| 161 | + domnamelen = len(self.domname.strip("\x00")) |
| 162 | + p = p[:3] + chr(domnamelen & 0xff) + p[4:] |
| 163 | + |
| 164 | + p += pay |
| 165 | + |
| 166 | + return p |
| 167 | + |
| 168 | +bind_layers(SNAP, VTP, code=0x2003) |
| 169 | + |
| 170 | +if __name__ == '__main__': |
| 171 | + interact(mydict=globals(), mybanner="VTP") |
0 commit comments