esp32_bluetooth_classic_sni.../libs/scapy/contrib/tzsp.py
Matheus Eduardo Garbelini 86890704fd initial commit
todo: add documentation & wireshark dissector
2021-08-31 19:51:03 +08:00

484 lines
14 KiB
Python
Executable file

# scapy.contrib.description = TaZmen Sniffer Protocol (TZSP)
# scapy.contrib.status = loads
"""
TZSP - TaZmen Sniffer Protocol
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:author: Thomas Tannhaeuser, hecke@naberius.de
:license: GPLv2
This module 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 (at your option) any later version.
This module 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.
:description:
This module provides Scapy layers for the TZSP protocol.
references:
- https://en.wikipedia.org/wiki/TZSP
- https://web.archive.org/web/20050404125022/http://www.networkchemistry.com/support/appnotes/an001_tzsp.html # noqa: E501
:NOTES:
- to allow Scapy to dissect this layer automatically, you need to bind the TZSP layer to UDP using # noqa: E501
the default TZSP port (0x9090), e.g.
bind_layers(UDP, TZSP, sport=TZSP_PORT_DEFAULT)
bind_layers(UDP, TZSP, dport=TZSP_PORT_DEFAULT)
- packet format definition from www.networkchemistry.com is different from the one given by wikipedia # noqa: E501
- seems Wireshark implements the wikipedia protocol version (didn't dive into their code) # noqa: E501
- observed (miss)behavior of Wireshark (2.2.6)
- fails to decode RSSI & SNR using short values - only one byte taken
- SNR is labeled as silence
- WlanRadioHdrSerial is labeled as Sensor MAC
- doesn't know the packet count tag (40 / 0x28)
"""
from scapy.compat import orb
from scapy.contrib.avs import AVSWLANHeader
from scapy.error import warning, Scapy_Exception
from scapy.fields import ByteField, ShortEnumField, IntField, FieldLenField, YesNoByteField # noqa: E501
from scapy.layers.dot11 import Packet, Dot11, PrismHeader
from scapy.layers.l2 import Ether
from scapy.fields import StrLenField, ByteEnumField, ShortField, XStrLenField
from scapy.packet import Raw
TZSP_PORT_DEFAULT = 0x9090
class TZSP(Packet):
TYPE_RX_PACKET = 0x00
TYPE_TX_PACKET = 0x01
TYPE_CONFIG = 0x03
TYPE_KEEPALIVE = TYPE_NULL = 0x04
TYPE_PORT = 0x05
TYPES = {
TYPE_RX_PACKET: 'RX_PACKET',
TYPE_TX_PACKET: 'TX_PACKET',
TYPE_CONFIG: 'CONFIG',
TYPE_NULL: 'KEEPALIVE/NULL',
TYPE_PORT: 'PORT',
}
ENCAPSULATED_ETHERNET = 0x01
ENCAPSULATED_IEEE_802_11 = 0x12
ENCAPSULATED_PRISM_HEADER = 0x77
ENCAPSULATED_WLAN_AVS = 0x7f
ENCAPSULATED_PROTOCOLS = {
ENCAPSULATED_ETHERNET: 'ETHERNET',
ENCAPSULATED_IEEE_802_11: 'IEEE 802.11',
ENCAPSULATED_PRISM_HEADER: 'PRISM HEADER',
ENCAPSULATED_WLAN_AVS: 'WLAN AVS'
}
ENCAPSULATED_PROTOCOL_CLASSES = {
ENCAPSULATED_ETHERNET: Ether,
ENCAPSULATED_IEEE_802_11: Dot11,
ENCAPSULATED_PRISM_HEADER: PrismHeader,
ENCAPSULATED_WLAN_AVS: AVSWLANHeader
}
fields_desc = [
ByteField('version', 0x01),
ByteEnumField('type', TYPE_RX_PACKET, TYPES),
ShortEnumField('encapsulated_protocol', ENCAPSULATED_ETHERNET, ENCAPSULATED_PROTOCOLS) # noqa: E501
]
def get_encapsulated_payload_class(self):
"""
get the class that holds the encapsulated payload of the TZSP packet
:return: class representing the payload, Raw() on error
"""
try:
return TZSP.ENCAPSULATED_PROTOCOL_CLASSES[self.encapsulated_protocol] # noqa: E501
except KeyError:
warning(
'unknown or invalid encapsulation type (%i) - returning payload as raw()' % self.encapsulated_protocol) # noqa: E501
return Raw
def guess_payload_class(self, payload):
if self.type == TZSP.TYPE_KEEPALIVE:
if len(payload):
warning('payload (%i bytes) in KEEPALIVE/NULL packet' % len(payload)) # noqa: E501
return Raw
else:
return _tzsp_guess_next_tag(payload)
def get_encapsulated_payload(self):
has_encapsulated_data = self.type == TZSP.TYPE_RX_PACKET or self.type == TZSP.TYPE_TX_PACKET # noqa: E501
if has_encapsulated_data:
end_tag_lyr = self.payload.getlayer(TZSPTagEnd)
if end_tag_lyr:
return end_tag_lyr.payload
else:
return None
def _tzsp_handle_unknown_tag(payload, tag_type):
payload_len = len(payload)
if payload_len < 2:
warning('invalid or unknown tag type (%i) and too short packet - treat remaining data as Raw' % tag_type) # noqa: E501
return Raw
tag_data_length = orb(payload[1])
tag_data_fits_in_payload = (tag_data_length + 2) <= payload_len
if not tag_data_fits_in_payload:
warning('invalid or unknown tag type (%i) and too short packet - treat remaining data as Raw' % tag_type) # noqa: E501
return Raw
warning('invalid or unknown tag type (%i)' % tag_type)
return TZSPTagUnknown
def _tzsp_guess_next_tag(payload):
"""
:return: class representing the next tag, Raw on error, None on missing payload # noqa: E501
"""
if not payload:
warning('missing payload')
return None
tag_type = orb(payload[0])
try:
tag_class_definition = _TZSP_TAG_CLASSES[tag_type]
except KeyError:
return _tzsp_handle_unknown_tag(payload, tag_type)
if type(tag_class_definition) is not dict:
return tag_class_definition
try:
length = orb(payload[1])
except IndexError:
length = None
if not length:
warning('no tag length given - packet to short')
return Raw
try:
return tag_class_definition[length]
except KeyError:
warning('invalid tag length {} for tag type {}'.format(length, tag_type)) # noqa: E501
return Raw
class _TZSPTag(Packet):
TAG_TYPE_PADDING = 0x00
TAG_TYPE_END = 0x01
TAG_TYPE_RAW_RSSI = 0x0a
TAG_TYPE_SNR = 0x0b
TAG_TYPE_DATA_RATE = 0x0c
TAG_TYPE_TIMESTAMP = 0x0d
TAG_TYPE_CONTENTION_FREE = 0x0f
TAG_TYPE_DECRYPTED = 0x10
TAG_TYPE_FCS_ERROR = 0x11
TAG_TYPE_RX_CHANNEL = 0x12
TAG_TYPE_PACKET_COUNT = 0x28
TAG_TYPE_RX_FRAME_LENGTH = 0x29
TAG_TYPE_WLAN_RADIO_HDR_SERIAL = 0x3c
TAG_TYPES = {
TAG_TYPE_PADDING: 'PADDING',
TAG_TYPE_END: 'END',
TAG_TYPE_RAW_RSSI: 'RAW_RSSI',
TAG_TYPE_SNR: 'SNR',
TAG_TYPE_DATA_RATE: 'DATA_RATE',
TAG_TYPE_TIMESTAMP: 'TIMESTAMP',
TAG_TYPE_CONTENTION_FREE: 'CONTENTION_FREE',
TAG_TYPE_DECRYPTED: 'DECRYPTED',
TAG_TYPE_FCS_ERROR: 'FCS_ERROR',
TAG_TYPE_RX_CHANNEL: 'RX_CHANNEL',
TAG_TYPE_PACKET_COUNT: 'PACKET_COUNT',
TAG_TYPE_RX_FRAME_LENGTH: 'RX_FRAME_LENGTH',
TAG_TYPE_WLAN_RADIO_HDR_SERIAL: 'WLAN_RADIO_HDR_SERIAL'
}
def guess_payload_class(self, payload):
return _tzsp_guess_next_tag(payload)
class TZSPStructureException(Scapy_Exception):
pass
class TZSPTagPadding(_TZSPTag):
"""
padding tag (should be ignored)
"""
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_PADDING, _TZSPTag.TAG_TYPES),
]
class TZSPTagEnd(Packet):
"""
last tag
"""
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_END, _TZSPTag.TAG_TYPES),
]
def guess_payload_class(self, payload):
"""
the type of the payload encapsulation is given be the outer TZSP layers attribute encapsulation_protocol # noqa: E501
"""
under_layer = self.underlayer
tzsp_header = None
while under_layer:
if isinstance(under_layer, TZSP):
tzsp_header = under_layer
break
under_layer = under_layer.underlayer
if tzsp_header:
return tzsp_header.get_encapsulated_payload_class()
else:
raise TZSPStructureException('missing parent TZSP header')
class TZSPTagRawRSSIByte(_TZSPTag):
"""
relative received signal strength - signed byte value
"""
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_RAW_RSSI, _TZSPTag.TAG_TYPES),
ByteField('len', 1),
ByteField('raw_rssi', 0)
]
class TZSPTagRawRSSIShort(_TZSPTag):
"""
relative received signal strength - signed short value
"""
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_RAW_RSSI, _TZSPTag.TAG_TYPES),
ByteField('len', 2),
ShortField('raw_rssi', 0)
]
class TZSPTagSNRByte(_TZSPTag):
"""
signal noise ratio - signed byte value
"""
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_SNR, _TZSPTag.TAG_TYPES),
ByteField('len', 1),
ByteField('snr', 0)
]
class TZSPTagSNRShort(_TZSPTag):
"""
signal noise ratio - signed short value
"""
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_SNR, _TZSPTag.TAG_TYPES),
ByteField('len', 2),
ShortField('snr', 0)
]
class TZSPTagDataRate(_TZSPTag):
"""
wireless link data rate
"""
DATA_RATE_UNKNOWN = 0x00
DATA_RATE_1 = 0x02
DATA_RATE_2 = 0x04
DATA_RATE_5_5 = 0x0B
DATA_RATE_6 = 0x0C
DATA_RATE_9 = 0x12
DATA_RATE_11 = 0x16
DATA_RATE_12 = 0x18
DATA_RATE_18 = 0x24
DATA_RATE_22 = 0x2C
DATA_RATE_24 = 0x30
DATA_RATE_33 = 0x42
DATA_RATE_36 = 0x48
DATA_RATE_48 = 0x60
DATA_RATE_54 = 0x6C
DATA_RATE_LEGACY_1 = 0x0A
DATA_RATE_LEGACY_2 = 0x14
DATA_RATE_LEGACY_5_5 = 0x37
DATA_RATE_LEGACY_11 = 0x6E
DATA_RATES = {
DATA_RATE_UNKNOWN: 'unknown',
DATA_RATE_1: '1 MB/s',
DATA_RATE_2: '2 MB/s',
DATA_RATE_5_5: '5.5 MB/s',
DATA_RATE_6: '6 MB/s',
DATA_RATE_9: '9 MB/s',
DATA_RATE_11: '11 MB/s',
DATA_RATE_12: '12 MB/s',
DATA_RATE_18: '18 MB/s',
DATA_RATE_22: '22 MB/s',
DATA_RATE_24: '24 MB/s',
DATA_RATE_33: '33 MB/s',
DATA_RATE_36: '36 MB/s',
DATA_RATE_48: '48 MB/s',
DATA_RATE_54: '54 MB/s',
DATA_RATE_LEGACY_1: '1 MB/s (legacy)',
DATA_RATE_LEGACY_2: '2 MB/s (legacy)',
DATA_RATE_LEGACY_5_5: '5.5 MB/s (legacy)',
DATA_RATE_LEGACY_11: '11 MB/s (legacy)',
}
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_DATA_RATE, _TZSPTag.TAG_TYPES),
ByteField('len', 1),
ByteEnumField('data_rate', DATA_RATE_UNKNOWN, DATA_RATES)
]
class TZSPTagTimestamp(_TZSPTag):
"""
MAC receive timestamp
"""
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_TIMESTAMP, _TZSPTag.TAG_TYPES),
ByteField('len', 4),
IntField('timestamp', 0)
]
class TZSPTagContentionFree(_TZSPTag):
"""
packet received in contention free period
"""
NO = 0x00
YES = 0x01
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_CONTENTION_FREE, _TZSPTag.TAG_TYPES), # noqa: E501
ByteField('len', 1),
YesNoByteField('contention_free', NO)
]
class TZSPTagDecrypted(_TZSPTag):
"""
packet was decrypted
"""
YES = 0x00
NO = 0x01
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_DECRYPTED, _TZSPTag.TAG_TYPES),
ByteField('len', 1),
YesNoByteField('decrypted', NO, config={'yes': YES, 'no': (NO, 0xff)})
]
class TZSPTagError(_TZSPTag):
"""
frame checksum error
"""
NO = 0x00
YES = 0x01
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_FCS_ERROR, _TZSPTag.TAG_TYPES),
ByteField('len', 1),
YesNoByteField('fcs_error', NO, config={'no': NO, 'yes': YES, 'reserved': (YES + 1, 0xff)}) # noqa: E501
]
class TZSPTagRXChannel(_TZSPTag):
"""
channel the sensor was on while receiving the frame
"""
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_RX_CHANNEL, _TZSPTag.TAG_TYPES), # noqa: E501
ByteField('len', 1),
ByteField('rx_channel', 0)
]
class TZSPTagPacketCount(_TZSPTag):
"""
packet counter
"""
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_PACKET_COUNT, _TZSPTag.TAG_TYPES), # noqa: E501
ByteField('len', 4),
IntField('packet_count', 0)
]
class TZSPTagRXFrameLength(_TZSPTag):
"""
received packet length
"""
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_RX_FRAME_LENGTH, _TZSPTag.TAG_TYPES), # noqa: E501
ByteField('len', 2),
ShortField('rx_frame_length', 0)
]
class TZSPTagWlanRadioHdrSerial(_TZSPTag):
"""
(vendor specific) unique capture device (sensor/AP) identifier
"""
fields_desc = [
ByteEnumField('type', _TZSPTag.TAG_TYPE_WLAN_RADIO_HDR_SERIAL, _TZSPTag.TAG_TYPES), # noqa: E501
FieldLenField('len', None, length_of='sensor_id', fmt='b'),
StrLenField('sensor_id', '', length_from=lambda pkt:pkt.len)
]
class TZSPTagUnknown(_TZSPTag):
"""
unknown tag type dummy
"""
fields_desc = [
ByteField('type', 0xff),
FieldLenField('len', None, length_of='data', fmt='b'),
XStrLenField('data', '', length_from=lambda pkt: pkt.len)
]
_TZSP_TAG_CLASSES = {
_TZSPTag.TAG_TYPE_PADDING: TZSPTagPadding,
_TZSPTag.TAG_TYPE_END: TZSPTagEnd,
_TZSPTag.TAG_TYPE_RAW_RSSI: {1: TZSPTagRawRSSIByte, 2: TZSPTagRawRSSIShort}, # noqa: E501
_TZSPTag.TAG_TYPE_SNR: {1: TZSPTagSNRByte, 2: TZSPTagSNRShort},
_TZSPTag.TAG_TYPE_DATA_RATE: TZSPTagDataRate,
_TZSPTag.TAG_TYPE_TIMESTAMP: TZSPTagTimestamp,
_TZSPTag.TAG_TYPE_CONTENTION_FREE: TZSPTagContentionFree,
_TZSPTag.TAG_TYPE_DECRYPTED: TZSPTagDecrypted,
_TZSPTag.TAG_TYPE_FCS_ERROR: TZSPTagError,
_TZSPTag.TAG_TYPE_RX_CHANNEL: TZSPTagRXChannel,
_TZSPTag.TAG_TYPE_PACKET_COUNT: TZSPTagPacketCount,
_TZSPTag.TAG_TYPE_RX_FRAME_LENGTH: TZSPTagRXFrameLength,
_TZSPTag.TAG_TYPE_WLAN_RADIO_HDR_SERIAL: TZSPTagWlanRadioHdrSerial
}