# 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 }